diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 7e3649a..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,16 +0,0 @@
-# http://editorconfig.org
-root = true
-
-[*]
-indent_style = space
-indent_size = 2
-end_of_line = lf
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
-
-[*.md]
-trim_trailing_whitespace = false
-
-[Makefile]
-indent_style = tab
diff --git a/.env b/.env
deleted file mode 100644
index 2978ce0..0000000
--- a/.env
+++ /dev/null
@@ -1,3 +0,0 @@
-BROWSER=none
-HOST=0.0.0.0
-PORT=7000
diff --git a/.eslintignore b/.eslintignore
index 96a82df..5f3a4c3 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,7 +1,10 @@
-src/**/*-test.js
-src/public
-src/routes/chart/ECharts/theme
-src/routes/chart/highCharts/mapdata
-src/locales/_build/
-src/locales/**/*.js
-docs/**/*.js
+node_modules
+dist
+.umi
+build
+public
+coverage
+*.config.js
+
+# ignore umi generated files under src
+src/.umi
diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index e71ebc8..0000000
--- a/.eslintrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "extends": "react-app",
- "rules": {
- "jsx-a11y/href-no-hash": "off",
- "no-console": "warn",
- "valid-jsdoc": "warn"
- }
-}
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..625454a
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,61 @@
+module.exports = {
+ root: true,
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module',
+ ecmaFeatures: { jsx: true },
+ },
+ plugins: [
+ '@typescript-eslint',
+ 'react',
+ 'react-hooks',
+ 'unused-imports',
+ 'prettier',
+ 'import',
+ 'jsx-a11y',
+ ],
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react/recommended',
+ 'plugin:react-hooks/recommended',
+ 'plugin:prettier/recommended',
+ ],
+ settings: {
+ react: { version: 'detect' },
+ 'import/resolver': {
+ typescript: {},
+ },
+ },
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ },
+ rules: {
+ 'prettier/prettier': 'error',
+ 'react/react-in-jsx-scope': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn',
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
+ ],
+ '@typescript-eslint/no-explicit-any': 'off',
+ 'unused-imports/no-unused-imports': 'warn',
+ 'no-console': [
+ 'warn',
+ {
+ allow: ['warn', 'error', 'info'],
+ },
+ ],
+ 'import/order': 'off',
+ },
+ overrides: [
+ {
+ files: ['*.ts', '*.tsx'],
+ rules: {
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
+ },
+ },
+ ],
+};
diff --git a/.github/issue_template.md b/.github/issue_template.md
deleted file mode 100644
index 1f0ad65..0000000
--- a/.github/issue_template.md
+++ /dev/null
@@ -1,8 +0,0 @@
-> 请酌情提供细节,并保持issue精简便于查阅
-> 1. 功能需求 => 详细说明
-> 2. Bug反馈
-> 2.1 简单描述下报错
-> 2.2 你期望什么结果
-> 2.3 如何操作导致的
-> 2.4 可提供运行环境信息
-> 3. 代码求助 => 可以提,未必及时答复
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..fd3a62f
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,76 @@
+++ name: CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ node-version: [18, 20]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+
+ - name: Enable corepack (pnpm)
+ run: |
+ corepack enable
+ corepack prepare pnpm@latest --activate
+
+ - name: Cache pnpm store
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.pnpm-store
+ ~/.cache/pnpm
+ key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Test (if present)
+ run: pnpm test --if-present
+
+ - name: Lint
+ run: pnpm run lint
+
+ - name: Build
+ run: pnpm run build
+
+ - name: Upload build (artifact)
+ if: success()
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist
+ path: ./dist
+
+ publish:
+ name: Publish to GitHub Pages
+ runs-on: ubuntu-latest
+ needs: build
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+ steps:
+ - name: Download artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: dist
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ publish_dir: ./dist
+ publish_branch: gh-pages
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 75b78a8..8ec33e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,14 @@
-coverage
-dist
-node_modules
-npm-debug.log
-yarn-error.log
+/node_modules
+/.env.local
+/.umirc.local.ts
+/config/config.local.ts
+/src/.umi
+/src/.umi-production
+/src/.umi-test
+/dist
+.swc
+.turbopack
+
+pnpm-lock.yaml
yarn.lock
-package-lock.json
-
-# ide
-.idea
-
-# Mac General
-.DS_Store
-.AppleDouble
-.LSOverride
-
-# umi
-.umi
-.umi-production
-
-# jslingui
-src/locales/_build
-src/locales/**/*.js
\ No newline at end of file
+npm-lock.json
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..0b44d45
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+registry=https://registry.npmjs.com/
+
diff --git a/.prettierignore b/.prettierignore
index 1e879f7..f511fed 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,6 +1,9 @@
-*.svg
-*.ejs
-.DS_Store
+node_modules
+dist
+.umi
+build
+coverage
+public
+node_modules
.umi
.umi-production
-src/locales/**/*.json
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 36301bc..70767cd 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,8 @@
{
- "semi": false,
+ "printWidth": 80,
"singleQuote": true,
- "trailingComma": "es5"
+ "trailingComma": "all",
+ "proseWrap": "never",
+ "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }],
+ "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"]
}
diff --git a/.stylelintrc.json b/.stylelintrc.json
deleted file mode 100644
index b533bdd..0000000
--- a/.stylelintrc.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
- "rules": {
- "declaration-empty-line-before": null,
- "no-descending-specificity": null,
- "selector-pseudo-class-no-unknown": null,
- "selector-pseudo-element-colon-notation": null
- }
-}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 66de02f..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-language: node_js
-node_js:
- - node
-script:
- - npm run build
-before_install:
- - yarn global add now
-after_script:
- - |
- if [[ $TRAVIS_BRANCH == 'master' ]]; then
- echo $PROD_SITE_NOW_CONFIG >> dist/vercel.json && echo $PROD_DOC_NOW_CONFIG >> docs/vercel.json;
- else
- echo $DEV_SITE_NOW_CONFIG >> dist/vercel.json && echo $DEV_DOC_NOW_CONFIG >> docs/vercel.json;
- fi
- - cd ./dist
- - now -A vercel.json -t $NOW_TOKEN --prod -c
- - cd ../docs
- - now -A vercel.json -t $NOW_TOKEN --prod -c
diff --git a/.umirc.js b/.umirc.js
deleted file mode 100644
index 8541a2c..0000000
--- a/.umirc.js
+++ /dev/null
@@ -1,145 +0,0 @@
-// https://umijs.org/config/
-import { resolve } from 'path'
-const fs = require('fs')
-const path = require('path')
-const lessToJs = require('less-vars-to-js')
-const isDevelopment = process.env.NODE_ENV === 'development'
-
-const { theme } = require('antd')
-
-const mapToken = theme.defaultAlgorithm(theme.defaultSeed)
-const v4Token = Object.fromEntries(
- Object.entries(mapToken).map(([k, v]) => [k, typeof v === 'string' ? v : String(v)])
-)
-
-// how to speed compile: https://umijs.org/guide/boost-compile-speed
-export default {
- // IMPORTANT! change next line to yours or delete. And hide in dev
- publicPath: isDevelopment ? '/' : 'https://cdn.antd-admin.zuiidea.com/',
- alias: {
- api: resolve(__dirname, './src/services/'),
- components: resolve(__dirname, './src/components'),
- config: resolve(__dirname, './src/utils/config'),
- themes: resolve(__dirname, './src/themes'),
- utils: resolve(__dirname, './src/utils'),
- },
- antd: false,
- // a lower cost way to genereate sourcemap, default is cheap-module-source-map, could save 60% time in dev hotload
- devtool: 'eval',
- dva: { immer: true },
- dynamicImport: {
- loading: 'components/Loader/Loader',
- },
- extraBabelPlugins: [
- [
- 'import',
- {
- libraryName: 'lodash',
- libraryDirectory: '',
- camel2DashComponentName: false,
- },
- 'lodash',
- ],
- [
- 'import',
- {
- libraryName: '@ant-design/icons',
- libraryDirectory: 'es/icons',
- camel2DashComponentName: false,
- },
- 'ant-design-icons',
- ],
- [
- 'macros'
- ]
- ],
- hash: true,
- ignoreMomentLocale: true,
- // umi3 comple node_modules by default, could be disable
- nodeModulesTransform: {
- type: 'none',
- exclude: [],
- },
- // Webpack Configuration
- proxy: {
- '/api/v1/weather': {
- target: 'https://api.seniverse.com/',
- changeOrigin: true,
- pathRewrite: { '^/api/v1/weather': '/v3/weather' },
- },
- },
- // Theme for antd
- // https://ant.design/docs/react/customize-theme
- theme: {
- ...v4Token,
- ...lessToJs(
- fs.readFileSync(path.join(__dirname, './src/themes/default.less'), 'utf8')
- )
- },
- webpack5: {},
- mfsu: {},
- chainWebpack: function (config, { webpack }) {
- !isDevelopment && config.merge({
- optimization: {
- minimize: false,
- splitChunks: {
- chunks: 'all',
- minSize: 30000,
- minChunks: 3,
- automaticNameDelimiter: '.',
- cacheGroups: {
- react: {
- name: 'react',
- priority: 20,
- test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
- },
- antd: {
- name: 'antd',
- priority: 20,
- test: /[\\/]node_modules[\\/](antd|@ant-design\/icons)[\\/]/,
- },
- 'echarts-gl': {
- name: 'echarts-gl',
- priority: 30,
- test: /[\\/]node_modules[\\/]echarts-gl[\\/]/,
- },
- zrender: {
- name: 'zrender',
- priority: 30,
- test: /[\\/]node_modules[\\/]zrender[\\/]/,
- },
- echarts: {
- name: 'echarts',
- priority: 20,
- test: /[\\/]node_modules[\\/](echarts|echarts-for-react|echarts-liquidfill)[\\/]/,
- },
- highcharts: {
- name: 'highcharts',
- priority: 20,
- test: /[\\/]node_modules[\\/]highcharts[\\/]/,
- },
- recharts: {
- name: 'recharts',
- priority: 20,
- test: /[\\/]node_modules[\\/]recharts[\\/]/,
- },
- draftjs: {
- name: 'draftjs',
- priority: 30,
- test: /[\\/]node_modules[\\/](draft-js|react-draft-wysiwyg|draftjs-to-html|draftjs-to-markdown)[\\/]/,
- },
- async: {
- chunks: 'async',
- minChunks: 2,
- name: 'async',
- maxInitialRequests: 1,
- minSize: 0,
- priority: 5,
- reuseExistingChunk: true,
- },
- },
- },
- },
- })
- },
-}
diff --git a/.umirc.ts b/.umirc.ts
new file mode 100644
index 0000000..6c474f2
--- /dev/null
+++ b/.umirc.ts
@@ -0,0 +1,15 @@
+import { defineConfig } from 'umi';
+
+export default defineConfig({
+ plugins: ['@umijs/plugins/dist/antd', '@umijs/plugins/dist/locale'],
+ esbuildMinifyIIFE: true,
+ locale: {
+ antd: true,
+ default: 'zh-CN',
+ baseNavigator: true,
+ useLocalStorage: true,
+ baseSeparator: '-',
+ title: true,
+ },
+ npmClient: 'pnpm',
+});
diff --git a/README-zh_CN.md b/README-zh_CN.md
index 5c2468e..f6746f1 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -1,7 +1,6 @@
-
-
+
@@ -9,80 +8,76 @@
-一套优秀的中后台前端解决方案
+一个简洁的基于 antd、umi 的 admin 模板.
-[](https://github.com/ant-design/ant-design)
-[](https://github.com/umijs/umi)
-[](https://github.com/zuiidea/antd-admin/issues)
-[](http://opensource.org/licenses/MIT)
-
-[](https://github.com/zuiidea/antd-admin/pulls)
-[](https://gitter.im/antd-admin/antd-admin)
+[](https://github.com/ant-design/ant-design) [](https://github.com/umijs/umi) [](https://github.com/zuiidea/antd-admin/issues) [](http://opensource.org/licenses/MIT) [](https://github.com/zuiidea/antd-admin/pulls) [](https://gitter.im/antd-admin/antd-admin)
-- 在线演示 - [https://antd-admin.zuiidea.com](https://antd-admin.zuiidea.com)
-- 使用文档 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/](https://doc.antd-admin.zuiidea.com/#/zh-cn/)
-- 常见问题 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/faq](https://doc.antd-admin.zuiidea.com/#/zh-cn/faq)
-- 更新日志 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/change-log](https://doc.antd-admin.zuiidea.com/#/zh-cn/change-log)
+
-[English](./README.md) | 简体中文
+English | [简体中文](./README-zh_CN.md)
-## 特性
+## 主要特性
-- 国际化,源码中抽离翻译字段,按需加载语言包
-- 动态权限,不同权限对应不同菜单
-- 优雅美观,Ant Design 设计体系
-- Mock 数据,本地数据调试
+- 基于 Ant Design 6 + Umi 框架
+- TypeScript 支持
+- 本地 mock(`mock/index.js`)用于开发阶段模拟接口
+- 轻量状态管理:`zustand`
+- 常用页面示例:Dashboard、菜单管理、AI Chat 示例等
+## 快速开始
-## 使用
-
-1. 下载项目代码。
+推荐使用 pnpm:
```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
+# 安装依赖
+pnpm install
+
+# 本地开发
+pnpm run dev
+
+# 打包构建
+pnpm run build
+
+# 代码检查与格式化
+pnpm run lint
+pnpm run format
```
-2. 进入目录安装依赖,国内用户推荐使用 [cnpm](https://cnpmjs.org) 进行加速。
+(本项目 `package.json` 中的脚本)
-```bash
-yarn install
-```
+## 项目结构(简要)
-或者
+- `src/`
+ - `layouts/` 全局布局
+ - `pages/` 页面路由与视图
+ - `components/` 公用组件
+ - `services/` 接口封装
+ - `store/` 全局状态管理
+ - `utils/` 工具与配置
-```bash
-npm install
-```
+## 配置与开发说明
-3. 启动本地服务器。
+- 本地 mock: `mock/index.js`,开发时可直接使用 mock 接口。
+- 路由示例:
+ - `/` 或 `/dashboard` — 仪表盘
+ - `/users` — 用户管理
+ - `/sys/menu` — 菜单管理
+ - `/ai/chat` — AI Chat 示例
-```bash
-npm run start
-```
+## 自定义与扩展
-4. 启动完成后打开浏览器访问 [http://localhost:7000](http://localhost:7000),如果需要更改启动端口,可在 `.env` 文件中配置。
+- 接口封装位于 `src/services`,按需修改或替换为真实后端。
+- 公共组件位于 `src/components`,例如 `DropOption` 可复用于表格操作列。
-5. 登录账号有2个,一个账号admin 密码admin;另一个账号guest 密码guest
+## 贡献
-> 更多信息请参考 [使用文档](https://doc.antd-admin.zuiidea.com/#/zh-cn/)。
+欢迎提交 issue 或 PR:
-
-## 支持环境
-
-现代浏览器。
-
-| [ ](http://godban.github.io/browsers-support-badges/)IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari | [ ](http://godban.github.io/browsers-support-badges/)Opera |
-| --------- | --------- | --------- | --------- | --------- |
-|IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
-
-## 参与贡献
-
-我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley:
-- 在你的公司或个人项目中使用 AntD Admin。
-- 通过 [Issue](http://github.com/zuiidea/antd-admin/issues) 报告 bug 或进行咨询。
-- 提交 [Pull Request](http://github.com/zuiidea/antd-admin/pulls) 改进代码。
-
-> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。
+- 修复 bug
+- 补充示例页面或组件
+- 优化类型定义或代码风格
diff --git a/README.md b/README.md
index c552210..c6f02f6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
-
-
+
@@ -11,77 +10,75 @@
An excellent front-end solution for enterprise applications.
-[](https://github.com/ant-design/ant-design)
-[](https://github.com/umijs/umi)
-[](https://github.com/zuiidea/antd-admin/issues)
-[](http://opensource.org/licenses/MIT)
-
-[](https://github.com/zuiidea/antd-admin/pulls)
-[](https://gitter.im/antd-admin/antd-admin)
+[](https://github.com/ant-design/ant-design) [](https://github.com/umijs/umi) [](https://github.com/zuiidea/antd-admin/issues) [](http://opensource.org/licenses/MIT) [](https://github.com/zuiidea/antd-admin/pulls) [](https://gitter.im/antd-admin/antd-admin)
-
+# antd-admin_next
-- Preview - [https://antd-admin.zuiidea.com](https://antd-admin.zuiidea.com)
-- Documentation - [https://superlbr.gitee.io/antd-admin](https://superlbr.gitee.io/antd-admin)
-- FAQ - [https://doc.antd-admin.zuiidea.com/#/faq](https://doc.antd-admin.zuiidea.com/#/faq)
-- ChangeLog - [https://doc.antd-admin.zuiidea.com/#/change-log](https://doc.antd-admin.zuiidea.com/#/change-log)
+Lightweight admin template built with Ant Design, Umi and TypeScript. This project aims to provide a clean, extensible starting point for building admin panels. It includes common pages, sample components, and a local mock server for development.
-English | [简体中文](./README-zh_CN.md)
+## Key Features
-## Features
+- Ant Design 6 + Umi
+- TypeScript-first codebase
+- Local mock server (`mock/index.js`) for development
+- Lightweight state with `zustand`
+- Example pages: Dashboard, Users, Menu management, AI Chat demo
-- Internationalization, extracting translation fields from source code, loading language packs on demand
-- Dynamic permissions, different permissions for different menus
-- Elegant and beautiful, Ant Design system
-- Mock data, local data debugging
+## Quick Start
-
-## Usage
-
-1. Clone project code.
+Recommended: use pnpm.
```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
+# install dependencies
+pnpm install
+
+# local development
+pnpm run dev
+
+# build for production
+pnpm run build
+
+# lint and format
+pnpm run lint
+pnpm run format
```
-2. Installation dependence.
+Scripts are defined in `package.json`.
-```bash
-yarn install
-```
+## Project Structure (summary)
-Or
+- `src/`
+ - `layouts/` global layout components
+ - `pages/` routes and views
+ - `components/` shared components
+ - `services/` API wrappers
+ - `store/` global state (zustand)
+ - `utils/` helpers and configuration
-```bash
-npm install
-```
+## Development notes
-3. Start local server.
+- Local mock: `mock/index.js` provides mocked API endpoints during development.
+- Common routes:
+ - `/` or `/dashboard` — Dashboard
+ - `/users` — Users list
+ - `/sys/menu` — Menu management
+ - `/ai/chat` — AI Chat demo
-```bash
-npm run start
-```
+## Customization
-4. After the startup is complete, open a browser and visit [http://localhost:7000](http://localhost:7000), If you need to change the startup port, you can configure it in the `.env` file.
+- Update API calls in `src/services` to point to real backends.
+- Reuse or extend components in `src/components` (for example `DropOption`).
-5. For login page, there're two account. Username: admin Password: admin / Username: guest Password: guest
+## GitHub Actions CI
-> More instructions at [documentation](https://doc.antd-admin.zuiidea.com)。
+This repository includes a GitHub Actions workflow that installs dependencies with `pnpm`, runs lint and builds the project. The workflow is at `.github/workflows/ci.yml`.
-
-## Browsers support
-
-Modern browsers.
-
-| [ ](http://godban.github.io/browsers-support-badges/)IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari | [ ](http://godban.github.io/browsers-support-badges/)Opera |
-| --------- | --------- | --------- | --------- | --------- |
-|IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
+If you want tests added to CI, add a test script to `package.json` and update the workflow accordingly.
## Contributing
-We very much welcome your contribution, you can build together with us in the following ways :smiley:
+Contributions welcome via issues and pull requests. Typical contributions:
-- Use Ant Design Pro in your daily work.
-- Submit [GitHub issues](http://github.com/zuiidea/antd-admin/issues)s to report bugs or ask questions.
-- Propose [Pull Request](http://github.com/zuiidea/antd-admin/pulls) to improve our code.
+- Bug fixes
+- Example pages or components
+- Type and lint improvements
\ No newline at end of file
diff --git a/assets/standard.md b/assets/standard.md
deleted file mode 100644
index f8cd8ce..0000000
--- a/assets/standard.md
+++ /dev/null
@@ -1,35 +0,0 @@
-### 开发环境
-
-- OS:Windows
-- 编辑器:Atom
-- cmd:Cmder
-
-### Atom 插件
-
-- [atom-beautify](https://atom.io/packages/atom-beautify)
-
- #### Less
-
- - [x] Beautify On Save
-
- #### Javascript
-
- - [ ] Disable Beautifying Language
-
- #### Markdown
-
- - [x] Beautify On Save
- - [x] Default Beautifier:Remark
-
- #### HTML
-
- - [x] Beautify On Save
-
-- [linter](https://atom.io/packages/linter)
- - [ ] Lint on Change
-- [linter-eslint](https://atom.io/packages/linter-eslint)
- - [x] Fix errors on save
-
-### Cmder 主题
-
-- [Panda-Theme-Cmder](https://github.com/HamidFaraji/Panda-Theme-Cmder)
diff --git a/docs/.nojekyll b/docs/.nojekyll
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/API-configuration.md b/docs/API-configuration.md
deleted file mode 100644
index 73ee148..0000000
--- a/docs/API-configuration.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# API configuration
-
-## Why
-
-In the use of `redux` or `dva` projects, we often write functions like the following `service` layer to make the code structure clearer, but it is easy to see that we will write a lot of similar code in `antd -admin@5.0`, using the more concise configuration method to achieve the same function.
-
-```javascript
-export async function login(data) {
- return request({
- url: '/api/v1/user/login',
- method: 'post',
- data,
- })
-}
-```
-
-## Configuration and use
-
-In the `src/services/api.js` file, you will see the following configuration object, the object's key is used to call the function name, the object's value is the requested `url`, the default request method is `GET`, The format of the value of the other request mode object is `'method url'`.
-
-```javascript
-export default {
- ...
- queryUser: '/user/:id',
- queryUserList: '/users',
- updateUser: 'Patch /user/:id',
- createUser: 'POST /user/:id',
- removeUser: 'DELETE /user/:id',
- removeUserList: 'POST /users/delete',
- ...
-}
-```
-
-Used in other files
-
-```javascript
-import { queryUser } from 'api'
-
-// in the general file
-...
-queryUser(option).then(data => console.log(data))
-...
-
-/ / Model file
-...
-yield call(queryUser, option)
-...
-```
-
-## Method to realize
-
-Refer to the `src/services/index.js` file to traverse the api configuration. Each property returns the corresponding encapsulated request function.
-
-```javascript
-import request from 'utils/request'
-import { apiPrefix } from 'utils/config'
-
-import api from './api'
-
-const gen = params => {
- let url = apiPrefix + params
- let method = 'GET'
-
- const paramsArray = params.split(' ')
- if (paramsArray.length === 2) {
- method = paramsArray[0]
- url = apiPrefix + paramsArray[1]
- }
-
- return function(data) {
- return request({
- url,
- data,
- method,
- })
- }
-}
-
-const APIFunction = {}
-for (const key in api) {
- APIFunction[key] = gen(api[key])
-}
-
-module.exports = APIFunction
-
-```
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index 41f72da..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-AntD Admin
-
-
-
-An excellent front-end solution for enterprise applications.
-
-[](https://github.com/ant-design/ant-design)
-[](https://github.com/umijs/umi)
-[](https://github.com/zuiidea/antd-admin/issues)
-[](http://opensource.org/licenses/MIT)
-
-[](https://github.com/zuiidea/antd-admin/pulls)
-[](https://gitter.im/antd-admin/antd-admin)
-
-
-
-- Preview - [https://antd-admin.zuiidea.com](https://antd-admin.zuiidea.com)
-- Documentation - [https://doc.antd-admin.zuiidea.com](https://doc.antd-admin.zuiidea.com)
-- FAQ - [https://doc.antd-admin.zuiidea.com/#/faq](https://doc.antd-admin.zuiidea.com/#/faq)
-- ChangeLog - [https://doc.antd-admin.zuiidea.com/#/change-log](https://doc.antd-admin.zuiidea.com/#/change-log)
-
-## Features
-
-- Internationalization, extracting translation fields from source code, loading language packs on demand
-- Dynamic permissions, different permissions for different menus
-- Elegant and beautiful, Ant Design system
-- Mock data, local data debugging
-
-
-## Usage
-
-1. Clone project code.
-
-```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
-```
-
-2. Installation dependence.
-
-```bash
-yarn install
-```
-
-Or
-
-```bash
-npm install
-```
-
-3. Start local server.
-
-```bash
-npm run start
-```
-
-4. After the startup is complete, open a browser and visit [http://localhost:7000](http://localhost:7000), If you need to change the startup port, you can configure it in the `.env` file.
-
-
-> More instructions at [documentation](https://doc.antd-admin.zuiidea.com)。
-
-
-## Browsers support
-
-Modern browsers.
-
-| [ ](http://godban.github.io/browsers-support-badges/)IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari | [ ](http://godban.github.io/browsers-support-badges/)Opera |
-| --------- | --------- | --------- | --------- | --------- |
-|IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
-
-## Contributing
-
-We very much welcome your contribution, you can build together with us in the following ways :smiley:
-
-- Use Ant Design Pro in your daily work.
-- Submit [GitHub issues](http://github.com/zuiidea/antd-admin/issues)s to report bugs or ask questions.
-- Propose [Pull Request](http://github.com/zuiidea/antd-admin/pulls) to improve our code.
diff --git a/docs/_media/favicon.ico b/docs/_media/favicon.ico
deleted file mode 100644
index 7ca2170..0000000
Binary files a/docs/_media/favicon.ico and /dev/null differ
diff --git a/docs/_media/logo.svg b/docs/_media/logo.svg
deleted file mode 100644
index ce7afe5..0000000
--- a/docs/_media/logo.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- Created with Sketch.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/_media/term_build.svg b/docs/_media/term_build.svg
deleted file mode 100644
index 5b1a6ea..0000000
--- a/docs/_media/term_build.svg
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- ➜ antd-admin git:( master ) ✗ ➜ antd-admin git:( master ) ✗ n ➜ antd-admin git:( master ) ✗ np ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm r ➜ antd-admin git:( master ) ✗ npm ru ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run b ➜ antd-admin git:( master ) ✗ npm run bu ➜ antd-admin git:( master ) ✗ npm run bui ➜ antd-admin git:( master ) ✗ npm run buil ➜ antd-admin git:( master ) ✗ npm run build Compiling ● webpack █████████████████████████ compiling (0%) Compiling ● webpack ███ ██████████████████████ building modules (10%) 2/2 modules 0 active Compiling ● webpack ███ ██████████████████████ building modules (11%) 11/13 modules 2 activ e node_modules/umi/lib/createHistory.js ● webpack ███ ██████████████████████ building modules (12%) 18/21 modules 3 activ node_modules/webpack/buildin/global.js ● webpack ██████ ███████████████████ building modules (23%) 110/112 modules 2 act ive node_modules/react-dom/cjs/react-dom.production.min.js ● webpack ██████ ███████████████████ building modules (23%) 115/118 modules 3 act node_modules/util/support/isBufferBrowser.js ● webpack ██████ ███████████████████ building modules (23%) 116/141 modules 25 ac tive node_modules/core-js/modules/_global.js ● webpack ███████ ██████████████████ building modules (25%) 130/142 modules 12 ac node_modules/path-browserify/index.js ● webpack ███████ ██████████████████ building modules (27%) 147/191 modules 44 ac node_modules/core-js/modules/_bind.js ● webpack █████████ ████████████████ building modules (35%) 215/256 modules 41 ac node_modules/core-js/modules/_inherit-if-required.js ● webpack ██████████ ███████████████ building modules (39%) 249/266 modules 17 ac node_modules/core-js/modules/web.dom.iterable.js ● webpack ███████████ ██████████████ building modules (42%) 267/307 modules 40 ac ...4m › postcss-loader › less-loader › src/pages/404.less ● webpack ████████████ █████████████ building modules (47%) 313/320 modules 7 act node_modules/prop-types/index.js ● webpack █████████████ ████████████ building modules (49%) 328/351 modules 23 ac node_modules/react-router-dom/es/index.js ● webpack █████████████ ████████████ building modules (51%) 343/354 modules 11 ac babel-loader › src/pages/chart/highCharts/HighChartsComponent.js ● webpack █████████████ ████████████ building modules (52%) 354/372 modules 18 ac src/locales/en/messages.json ● webpack ██████████████ ███████████ building modules (54%) 372/394 modules 22 ac babel-loader › src/utils/theme.js ● webpack ███████████████ ██████████ building modules (57%) 395/422 modules 27 ac node_modules/@babel/runtime/helpers/arrayWithoutHoles.js ● webpack ███████████████ ██████████ building modules (60%) 424/455 modules 31 ac node_modules/antd/es/card/Meta.js ● webpack ████████████████ █████████ building modules (61%) 427/463 modules 36 ac node_modules/antd/es/_util/throttleByAnimationFrame.js ● webpack ████████████████ █████████ building modules (64%) 458/477 modules 19 ac node_modules/antd/es/radio/group.js ● webpack █████████████████ ████████ building modules (67%) 476/497 modules 21 ac babel-loader › src/pages/chart/highCharts/mapdata/europe.js ● webpack █████████████████ ████████ building modules (66%) 488/514 modules 26 ac node_modules/react-router-dom/node_modules/warning/warning.js ● webpack █████████████████ ████████ building modules (67%) 493/518 modules 25 ac ...der › less-loader › src/pages/chart/Recharts/Container.less ● webpack █████████████████ ████████ building modules (67%) 505/525 modules 20 ac node_modules/omit.js/es/index.js ● webpack █████████████████ ████████ building modules (67%) 532/559 modules 27 ac node_modules/highcharts-exporting/exporting.js ● webpack █████████████████ ████████ building modules (67%) 574/598 modules 24 ac node_modules/antd/es/date-picker/locale/en_US.js ● webpack █████████████████ ████████ building modules (68%) 589/608 modules 19 ac node_modules/draft-js/lib/ContentState.js ● webpack █████████████████ ████████ building modules (67%) 616/641 modules 25 ac node_modules/react-router/es/matchPath.js ● webpack █████████████████ ████████ building modules (67%) 656/679 modules 23 ac node_modules/draft-js/lib/DraftEntityInstance.js ● webpack █████████████████ ████████ building modules (67%) 665/690 modules 25 ac node_modules/axios/lib/utils.js ● webpack █████████████████ ████████ building modules (67%) 685/712 modules 27 ac node_modules/add-dom-event-listener/lib/index.js ● webpack █████████████████ ████████ building modules (68%) 735/756 modules 21 ac node_modules/lodash/_objectToString.js ● webpack █████████████████ ████████ building modules (68%) 737/758 modules 21 ac node_modules/lodash/_getRawTag.js ● webpack █████████████████ ████████ building modules (67%) 756/787 modules 31 ac node_modules/add-dom-event-listener/lib/EventObject.js ● webpack █████████████████ ████████ building modules (67%) 771/800 modules 29 ac node_modules/core-js/library/modules/_add-to-unscopables.js ● webpack █████████████████ ████████ building modules (67%) 781/808 modules 27 ac node_modules/core-js/library/modules/_object-dp.js ● webpack █████████████████ ████████ building modules (68%) 817/836 modules 19 ac node_modules/size-sensor/lib/index.js ● webpack █████████████████ ████████ building modules (68%) 845/870 modules 25 ac node_modules/is-buffer/index.js ● webpack █████████████████ ████████ building modules (68%) 892/913 modules 21 ac node_modules/echarts/lib/util/model.js ● webpack █████████████████ ████████ building modules (68%) 933/963 modules 30 ac node_modules/echarts-liquidfill/node_modules/echarts/lib/model/Series.js ● webpack █████████████████ ████████ building modules (68%) 978/996 modules 18 ac node_modules/lodash/_baseFor.js ● webpack █████████████████ ████████ building modules (68%) 1000/1029 modules 29 active node_modules/zrender/lib/contain/text.js ● webpack █████████████████ ████████ building modules (68%) 1029/1056 modules 27 ...e_modules/echarts-liquidfill/node_modules/echarts/lib/model/mixin/dataFormat. js ● webpack █████████████████ ████████ building modules (68%) 1059/1077 modules 18 node_modules/lodash/_createBaseFor.js ● webpack █████████████████ ████████ building modules (68%) 1101/1133 modules 32 node_modules/zrender/lib/graphic/helper/image.js ● webpack █████████████████ ████████ building modules (68%) 1155/1175 modules 20 node_modules/lodash/_baseGetAllKeys.js ● webpack ██████████████████ ███████ building modules (69%) 1212/1231 modules 19 node_modules/size-sensor/lib/sensors/resizeObserver.js ● webpack █████████████████ ████████ building modules (68%) 1266/1296 modules 30 node_modules/d3-shape/src/curve/cardinalClosed.js ● webpack █████████████████ ████████ building modules (68%) 1329/1357 modules 28 ...ules/echarts-liquidfill/node_modules/echarts/lib/data/helper/dataStackHelper. ● webpack ██████████████████ ███████ building modules (69%) 1392/1409 modules 17 node_modules/d3-collection/src/set.js ● webpack ██████████████████ ███████ building modules (69%) 1449/1467 modules 18 node_modules/component-classes/index.js ● webpack █████████████████ ████████ building modules (68%) 1481/1507 modules 26 node_modules/d3-time/src/second.js ● webpack ██████████████████ ███████ building modules (69%) 1534/1551 modules 17 node_modules/echarts-liquidfill/src/liquidFillView.js ● webpack ██████████████████ ███████ building modules (69%) 1594/1607 modules 13 node_modules/rc-animate/es/CSSMotion.js ● webpack █████████████████ ████████ building modules (68%) 1651/1691 modules 40 node_modules/echarts/lib/component/gridSimple.js ● webpack ██████████████████ ███████ building modules (69%) 1700/1713 modules 13 node_modules/echarts/lib/coord/calendar/prepareCustom.js ● webpack █████████████████ ████████ building modules (68%) 1739/1771 modules 32 node_modules/echarts/lib/component/tooltip/TooltipContent.js ● webpack ██████████████████ ███████ building modules (69%) 1780/1807 modules 27 node_modules/echarts/lib/chart/scatter/ScatterView.js ● webpack ██████████████████ ███████ building modules (69%) 1781/1808 modules 27 node_modules/echarts/lib/chart/scatter/ScatterSeries.js ● webpack █████████████████ ████████ building modules (68%) 1819/1852 modules 33 node_modules/echarts/lib/chart/graph/categoryFilter.js ● webpack ██████████████████ ███████ building modules (69%) 1867/1880 modules 13 node_modules/echarts/lib/chart/sankey/SankeySeries.js ● webpack ██████████████████ ███████ building modules (69%) 1916/1944 modules 28 node_modules/echarts/lib/component/dataZoom/DataZoomView.js ● webpack ██████████████████ ███████ building modules (69%) 1966/1993 modules 27 node_modules/echarts/lib/chart/sunburst/SunburstPiece.js ● webpack ██████████████████ ███████ building modules (69%) 2002/2030 modules 28 node_modules/draft-js/lib/CompositeDraftDecorator.js ● webpack ██████████████████ ███████ building modules (69%) 2010/2034 modules 24 node_modules/echarts/lib/component/marker/MarkerModel.js ● webpack ██████████████████ ███████ building modules (69%) 2031/2047 modules 16 babel-loader › src/pages/dashboard/components/browser.js ● webpack ██████████████████ ███████ building modules (69%) 2081/2100 modules 19 node_modules/array-tree-filter/lib/index.js ● webpack ██████████████████ ███████ building modules (69%) 2105/2122 modules 17 node_modules/echarts/lib/component/dataZoom/SelectZoomModel.js ● webpack ██████████████████ ███████ building modules (69%) 2108/2123 modules 15 ● webpack ██████████████████ ███████ building modules (69%) 2117/2127 modules 10 node_modules/react-router-redux/es/middleware.js ● webpack ██████████████████ ███████ building modules (69%) 2122/2130 modules 8 a ctive node_modules/react-router-redux/es/actions.js ● webpack ██████████████████ ███████ building modules (69%) 2143/2167 modules 24 node_modules/react-redux/es/connect/connect.js ● webpack ██████████████████ ███████ building modules (69%) 2185/2203 modules 18 node_modules/rc-dialog/es/DialogWrap.js ● webpack ██████████████████ ███████ building modules (69%) 2237/2265 modules 28 node_modules/antd/es/tag/index.js ● webpack ██████████████████ ███████ building modules (69%) 2283/2301 modules 18 babel-loader › src/pages/user/components/Filter.js ● webpack ██████████████████ ███████ building modules (69%) 2327/2338 modules 11 node_modules/rc-util/es/getScrollBarSize.js ● webpack ██████████████████ ███████ building modules (69%) 2339/2357 modules 18 node_modules/async-validator/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2347/2363 modules 16 node_modules/lodash/_createCurry.js ● webpack ██████████████████ ███████ building modules (69%) 2384/2400 modules 16 node_modules/rmc-feedback/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2402/2422 modules 20 node_modules/dom-align/es/getAlignOffset.js ● webpack ██████████████████ ███████ building modules (69%) 2426/2438 modules 12 node_modules/dva-core/lib/prefixType.js ● webpack ██████████████████ ███████ building modules (69%) 2430/2438 modules 8 a ● webpack ██████████████████ ███████ building modules (69%) 2451/2471 modules 20 node_modules/antd/es/badge/index.js ● webpack ██████████████████ ███████ building modules (69%) 2485/2494 modules 9 a node_modules/fbjs/lib/emptyObject.js ● webpack ██████████████████ ███████ building modules (69%) 2490/2520 modules 30 node_modules/antd/es/date-picker/WeekPicker.js ● webpack ██████████████████ ███████ building modules (69%) 2512/2524 modules 12 node_modules/react-side-effect/lib/index.js ● webpack ██████████████████ ███████ building modules (69%) 2519/2529 modules 10 node_modules/redux-saga/lib/internal/runSaga.js ● webpack ██████████████████ ███████ building modules (69%) 2524/2532 modules 8 a node_modules/antd/es/layout/Sider.js ● webpack ██████████████████ ███████ building modules (69%) 2540/2549 modules 9 a node_modules/antd/es/_util/getDataOrAriaProps.js ● webpack ██████████████████ ███████ building modules (69%) 2554/2561 modules 7 a node_modules/redux-saga/lib/internal/sagaHelpers/index.js ● webpack ██████████████████ ███████ building modules (69%) 2577/2581 modules 4 a node_modules/rc-switch/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2581/2589 modules 8 a node_modules/rc-drawer/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2604/2610 modules 6 a node_modules/rc-calendar/es/date/DateInput.js ● webpack ██████████████████ ███████ finish module graph (70%) ● webpack ███████████████████ ██████ optimizing (73%) ● webpack ████████████████████ █████ advanced chunk modules optimization (79%) ● webpack ██████████████████████ ███ after hashing (88%) ● webpack ███████████████████████ ██ additional chunk assets processing (90%) ● webpack ███████████████████████ ██ chunk asset optimization (92%) UglifyJSPlugi n ● webpack ███████████████████████ ██ after chunk asset optimization (92%) ➜ antd-admin git:( master ) ✗ npm run build > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin > umi build [12:40:22] webpack compiled in 15s 56ms DONE Compiled successfully in 15062ms 12:40:22 File sizes after gzip: 887 B dist/ p__dashboard__model.js.async.js 877 B dist/ precache-manifest.6d2a703c437c4f28b5f6e072817bbe6e.js 786 B dist/ p__UIElement__editor__index.async.js 764 B dist/ i18n-0.async.js 652 B dist/ p__login__index.chunk.css 614 B dist/ p__user__$id__models__detail.js.async.js 599 B dist/ p__post__model.js.async.js 589 B dist/ p__user__$id__index.async.js 569 B dist/ service-worker.js 530 B dist/ p__request__index.chunk.css 515 B dist/ p__login__model.js.async.js 419 B dist/ p__index.async.js 245 B dist/ p__chart__ECharts__index.chunk.css 230 B dist/ p__chart__Recharts__index.chunk.css 138 B dist/ p__user__$id__index.chunk.css 74 B dist/ p__chart__highCharts__index.chunk.css 66 B dist/ p__user__index.chunk.css 66 B dist/ p__post__index.chunk.css The bundle size is significantly larger than recommended. Consider reducing it with code splitting: https://goo.gl/9VhYWB You can also analyze the project dependencies: https://goo.gl/LeUzfb ➜ antd-admin git:( master ) ✗
-
\ No newline at end of file
diff --git a/docs/_media/term_i18n.svg b/docs/_media/term_i18n.svg
deleted file mode 100644
index a287916..0000000
--- a/docs/_media/term_i18n.svg
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- ➜ antd-admin git:( master ) ✗ ➜ antd-admin git:( master ) ✗ n ➜ antd-admin git:( master ) ✗ np ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm r ➜ antd-admin git:( master ) ✗ npm ru ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run a ➜ antd-admin git:( master ) ✗ npm run ad ➜ antd-admin git:( master ) ✗ npm run add ➜ antd-admin git:( master ) ✗ npm run add- ➜ antd-admin git:( master ) ✗ npm run add-l ➜ antd-admin git:( master ) ✗ npm run add-lo ➜ antd-admin git:( master ) ✗ npm run add-loc ➜ antd-admin git:( master ) ✗ npm run add-loca ➜ antd-admin git:( master ) ✗ npm run add-local ➜ antd-admin git:( master ) ✗ npm run add-locale ➜ antd-admin git:( master ) ✗ npm run add-locale ➜ antd-admin git:( master ) ✗ npm run add-locale j ➜ antd-admin git:( master ) ✗ npm run add-locale ja ➜ antd-admin git:( master ) ✗ npm run e ➜ antd-admin git:( master ) ✗ npm run ex ➜ antd-admin git:( master ) ✗ npm run ext ➜ antd-admin git:( master ) ✗ npm run extr ➜ antd-admin git:( master ) ✗ npm run extra ➜ antd-admin git:( master ) ✗ npm run extrac ➜ antd-admin git:( master ) ✗ npm run extract ➜ antd-admin git:( master ) ✗ npm run add-locale ja > antd-admin@5.0.0-beta add-locale /Users/zuiidea/web/antd-admin > lingui add-locale "ja" Added locale ja . (use " lingui extract " to extract messages) ➜ antd-admin git:( master ) ✗ npm run extract > antd-admin@5.0.0-beta extract /Users/zuiidea/web/antd-admin > lingui extract ➜ antd-admin git:( master ) ✗ npm run t ➜ antd-admin git:( master ) ✗ npm run tr ➜ antd-admin git:( master ) ✗ npm run tra ➜ antd-admin git:( master ) ✗ npm run tran ➜ antd-admin git:( master ) ✗ npm run trans ➜ antd-admin git:( master ) ✗ npm run trans: ➜ antd-admin git:( master ) ✗ npm run trans:o ➜ antd-admin git:( master ) ✗ npm run trans:on ➜ antd-admin git:( master ) ✗ npm run trans:onl Catalog statistics: ┌─────────────┬─────────────┬─────────┐ │ Language │ Total count │ Missing │ ├─────────────┼─────────────┼─────────┤ │ en (source) │ 52 │ - │ │ ja │ 52 │ 52 │ │ zh │ 52 │ 0 │ └─────────────┴─────────────┴─────────┘ (use " lingui add-locale <language> " to add more locales) (use " lingui extract " to update catalogs with new messages) (use " lingui compile " to compile catalogs for production) ➜ antd-admin git:( master ) ✗ npm run trans:only ➜ antd-admin git:( master ) ✗ npm run trans:only > antd-admin@5.0.0-beta trans:only /Users/zuiidea/web/antd-admin > node ./scripts/translate.js add to skip: Publish Date add to skip: Reset add to skip: Search add to skip: Search Name add to skip: Send add to skip: Sign in add to skip: Sign out add to skip: Switch Theme add to skip: Tags add to skip: The input is not valid E-mail! add to skip: The input is not valid phone! add to skip: Title add to skip: Total {total} Items add to skip: Unpublished add to skip: Update add to skip: Update User add to skip: Username add to skip: Views add to skip: Visibility add to skip: You have viewed all notifications. completed: en -> zh start: en -> ja link: en -> ja: /dashboard -> /ja/dashboard youdao: en -> ja: Add Param -> Param の追加 youdao: en -> ja: Address -> アドレス youdao: en -> ja: Age -> 年齢 youdao: en -> ja: Are you sure delete this record? -> このレコードを削除してもよ ろしいですか? youdao: en -> ja: Author -> 著者 youdao: en -> ja: Avatar -> アバター youdao: en -> ja: Categories -> カテゴリ youdao: en -> ja: Clear notifications -> 通知のクリア youdao: en -> ja: Comments -> コメント youdao: en -> ja: Create -> 作成 youdao: en -> ja: Create User -> ユーザーの作成 same: en -> ja: CreateTime youdao: en -> ja: Dark -> 暗い youdao: en -> ja: Delete -> 削除 youdao: en -> ja: Email -> 電子メール youdao: en -> ja: Female -> 女性 youdao: en -> ja: Gender -> 性別 youdao: en -> ja: Hi, -> こんにちは youdao: en -> ja: Image -> イメージ youdao: en -> ja: Light -> 光 youdao: en -> ja: Male -> 男性 youdao: en -> ja: Name -> 名前 youdao: en -> ja: NickName -> ニックネーム youdao: en -> ja: Not Found -> 見つからない youdao: en -> ja: Operation -> 操作 same: en -> ja: Params youdao: en -> ja: Password -> パスワード youdao: en -> ja: Phone -> 電話 youdao: en -> ja: Pick an address -> 住所の選択 youdao: en -> ja: Please pick an address -> 住所を選択してください same: en -> ja: Publised youdao: en -> ja: Publish Date -> 発行日 youdao: en -> ja: Reset -> リセット youdao: en -> ja: Search -> 検索 youdao: en -> ja: Search Name -> 検索名 youdao: en -> ja: Send -> 送信 youdao: en -> ja: Sign in -> サインイン youdao: en -> ja: Sign out -> サインアウト youdao: en -> ja: Switch Theme -> テーマを切り替える youdao: en -> ja: Tags -> タグ youdao: en -> ja: The input is not valid E-mail! -> 入力が有効な電子メールではあ りません! youdao: en -> ja: The input is not valid phone! -> 入力は有効な電話ではありません ! youdao: en -> ja: Title -> タイトル youdao: en -> ja: Total {total} Items -> 合計{total}項目 youdao: en -> ja: Unpublished -> 未発表 youdao: en -> ja: Update -> 更新 youdao: en -> ja: Update User -> ユーザーの更新 youdao: en -> ja: Username -> 名 youdao: en -> ja: Views -> 表示モード youdao: en -> ja: Visibility -> 可視 性 youdao: en -> ja: You have viewed all notifications. -> すべての通知を表示しまし た。 completed: en -> ja All translations have been completed. ➜ antd-admin git:( master ) ✗
-
\ No newline at end of file
diff --git a/docs/_media/term_js_build.svg b/docs/_media/term_js_build.svg
deleted file mode 100644
index ff7489f..0000000
--- a/docs/_media/term_js_build.svg
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ➜ antd-admin git:( master ) ✗ ➜ antd-admin git:( master ) ✗ n ➜ antd-admin git:( master ) ✗ np ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm r ➜ antd-admin git:( master ) ✗ npm ru ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run b ➜ antd-admin git:( master ) ✗ npm run bu ➜ antd-admin git:( master ) ✗ npm run bui ➜ antd-admin git:( master ) ✗ npm run buil ➜ antd-admin git:( master ) ✗ npm run build Compiling ● webpack █████████████████████████ compiling (0%) Compiling ● webpack ███ ██████████████████████ building modules (10%) 2/2 modules 0 active Compiling ● webpack ███ ██████████████████████ building modules (11%) 11/13 modules 2 activ e node_modules/umi/lib/createHistory.js ● webpack ███ ██████████████████████ building modules (12%) 18/21 modules 3 activ node_modules/webpack/buildin/global.js ● webpack ██████ ███████████████████ building modules (23%) 110/112 modules 2 act ive node_modules/react-dom/cjs/react-dom.production.min.js ● webpack ██████ ███████████████████ building modules (23%) 115/118 modules 3 act node_modules/util/support/isBufferBrowser.js ● webpack ██████ ███████████████████ building modules (23%) 116/141 modules 25 ac tive node_modules/core-js/modules/_global.js ● webpack ███████ ██████████████████ building modules (25%) 130/142 modules 12 ac node_modules/path-browserify/index.js ● webpack ███████ ██████████████████ building modules (27%) 147/191 modules 44 ac node_modules/core-js/modules/_bind.js ● webpack █████████ ████████████████ building modules (35%) 215/256 modules 41 ac node_modules/core-js/modules/_inherit-if-required.js ● webpack ██████████ ███████████████ building modules (39%) 249/266 modules 17 ac node_modules/core-js/modules/web.dom.iterable.js ● webpack ███████████ ██████████████ building modules (42%) 267/307 modules 40 ac ...4m › postcss-loader › less-loader › src/pages/404.less ● webpack ████████████ █████████████ building modules (47%) 313/320 modules 7 act node_modules/prop-types/index.js ● webpack █████████████ ████████████ building modules (49%) 328/351 modules 23 ac node_modules/react-router-dom/es/index.js ● webpack █████████████ ████████████ building modules (51%) 343/354 modules 11 ac babel-loader › src/pages/chart/highCharts/HighChartsComponent.js ● webpack █████████████ ████████████ building modules (52%) 354/372 modules 18 ac src/locales/en/messages.json ● webpack ██████████████ ███████████ building modules (54%) 372/394 modules 22 ac babel-loader › src/utils/theme.js ● webpack ███████████████ ██████████ building modules (57%) 395/422 modules 27 ac node_modules/@babel/runtime/helpers/arrayWithoutHoles.js ● webpack ███████████████ ██████████ building modules (60%) 424/455 modules 31 ac node_modules/antd/es/card/Meta.js ● webpack ████████████████ █████████ building modules (61%) 427/463 modules 36 ac node_modules/antd/es/_util/throttleByAnimationFrame.js ● webpack ████████████████ █████████ building modules (64%) 458/477 modules 19 ac node_modules/antd/es/radio/group.js ● webpack █████████████████ ████████ building modules (67%) 476/497 modules 21 ac babel-loader › src/pages/chart/highCharts/mapdata/europe.js ● webpack █████████████████ ████████ building modules (66%) 488/514 modules 26 ac node_modules/react-router-dom/node_modules/warning/warning.js ● webpack █████████████████ ████████ building modules (67%) 493/518 modules 25 ac ...der › less-loader › src/pages/chart/Recharts/Container.less ● webpack █████████████████ ████████ building modules (67%) 505/525 modules 20 ac node_modules/omit.js/es/index.js ● webpack █████████████████ ████████ building modules (67%) 532/559 modules 27 ac node_modules/highcharts-exporting/exporting.js ● webpack █████████████████ ████████ building modules (67%) 574/598 modules 24 ac node_modules/antd/es/date-picker/locale/en_US.js ● webpack █████████████████ ████████ building modules (68%) 589/608 modules 19 ac node_modules/draft-js/lib/ContentState.js ● webpack █████████████████ ████████ building modules (67%) 616/641 modules 25 ac node_modules/react-router/es/matchPath.js ● webpack █████████████████ ████████ building modules (67%) 656/679 modules 23 ac node_modules/draft-js/lib/DraftEntityInstance.js ● webpack █████████████████ ████████ building modules (67%) 665/690 modules 25 ac node_modules/axios/lib/utils.js ● webpack █████████████████ ████████ building modules (67%) 685/712 modules 27 ac node_modules/add-dom-event-listener/lib/index.js ● webpack █████████████████ ████████ building modules (68%) 735/756 modules 21 ac node_modules/lodash/_objectToString.js ● webpack █████████████████ ████████ building modules (68%) 737/758 modules 21 ac node_modules/lodash/_getRawTag.js ● webpack █████████████████ ████████ building modules (67%) 756/787 modules 31 ac node_modules/add-dom-event-listener/lib/EventObject.js ● webpack █████████████████ ████████ building modules (67%) 771/800 modules 29 ac node_modules/core-js/library/modules/_add-to-unscopables.js ● webpack █████████████████ ████████ building modules (67%) 781/808 modules 27 ac node_modules/core-js/library/modules/_object-dp.js ● webpack █████████████████ ████████ building modules (68%) 817/836 modules 19 ac node_modules/size-sensor/lib/index.js ● webpack █████████████████ ████████ building modules (68%) 845/870 modules 25 ac node_modules/is-buffer/index.js ● webpack █████████████████ ████████ building modules (68%) 892/913 modules 21 ac node_modules/echarts/lib/util/model.js ● webpack █████████████████ ████████ building modules (68%) 933/963 modules 30 ac node_modules/echarts-liquidfill/node_modules/echarts/lib/model/Series.js ● webpack █████████████████ ████████ building modules (68%) 978/996 modules 18 ac node_modules/lodash/_baseFor.js ● webpack █████████████████ ████████ building modules (68%) 1000/1029 modules 29 active node_modules/zrender/lib/contain/text.js ● webpack █████████████████ ████████ building modules (68%) 1029/1056 modules 27 ...e_modules/echarts-liquidfill/node_modules/echarts/lib/model/mixin/dataFormat. js ● webpack █████████████████ ████████ building modules (68%) 1059/1077 modules 18 node_modules/lodash/_createBaseFor.js ● webpack █████████████████ ████████ building modules (68%) 1101/1133 modules 32 node_modules/zrender/lib/graphic/helper/image.js ● webpack █████████████████ ████████ building modules (68%) 1155/1175 modules 20 node_modules/lodash/_baseGetAllKeys.js ● webpack ██████████████████ ███████ building modules (69%) 1212/1231 modules 19 node_modules/size-sensor/lib/sensors/resizeObserver.js ● webpack █████████████████ ████████ building modules (68%) 1266/1296 modules 30 node_modules/d3-shape/src/curve/cardinalClosed.js ● webpack █████████████████ ████████ building modules (68%) 1329/1357 modules 28 ...ules/echarts-liquidfill/node_modules/echarts/lib/data/helper/dataStackHelper. ● webpack ██████████████████ ███████ building modules (69%) 1392/1409 modules 17 node_modules/d3-collection/src/set.js ● webpack ██████████████████ ███████ building modules (69%) 1449/1467 modules 18 node_modules/component-classes/index.js ● webpack █████████████████ ████████ building modules (68%) 1481/1507 modules 26 node_modules/d3-time/src/second.js ● webpack ██████████████████ ███████ building modules (69%) 1534/1551 modules 17 node_modules/echarts-liquidfill/src/liquidFillView.js ● webpack ██████████████████ ███████ building modules (69%) 1594/1607 modules 13 node_modules/rc-animate/es/CSSMotion.js ● webpack █████████████████ ████████ building modules (68%) 1651/1691 modules 40 node_modules/echarts/lib/component/gridSimple.js ● webpack ██████████████████ ███████ building modules (69%) 1700/1713 modules 13 node_modules/echarts/lib/coord/calendar/prepareCustom.js ● webpack █████████████████ ████████ building modules (68%) 1739/1771 modules 32 node_modules/echarts/lib/component/tooltip/TooltipContent.js ● webpack ██████████████████ ███████ building modules (69%) 1780/1807 modules 27 node_modules/echarts/lib/chart/scatter/ScatterView.js ● webpack ██████████████████ ███████ building modules (69%) 1781/1808 modules 27 node_modules/echarts/lib/chart/scatter/ScatterSeries.js ● webpack █████████████████ ████████ building modules (68%) 1819/1852 modules 33 node_modules/echarts/lib/chart/graph/categoryFilter.js ● webpack ██████████████████ ███████ building modules (69%) 1867/1880 modules 13 node_modules/echarts/lib/chart/sankey/SankeySeries.js ● webpack ██████████████████ ███████ building modules (69%) 1916/1944 modules 28 node_modules/echarts/lib/component/dataZoom/DataZoomView.js ● webpack ██████████████████ ███████ building modules (69%) 1966/1993 modules 27 node_modules/echarts/lib/chart/sunburst/SunburstPiece.js ● webpack ██████████████████ ███████ building modules (69%) 2002/2030 modules 28 node_modules/draft-js/lib/CompositeDraftDecorator.js ● webpack ██████████████████ ███████ building modules (69%) 2010/2034 modules 24 node_modules/echarts/lib/component/marker/MarkerModel.js ● webpack ██████████████████ ███████ building modules (69%) 2031/2047 modules 16 babel-loader › src/pages/dashboard/components/browser.js ● webpack ██████████████████ ███████ building modules (69%) 2081/2100 modules 19 node_modules/array-tree-filter/lib/index.js ● webpack ██████████████████ ███████ building modules (69%) 2105/2122 modules 17 node_modules/echarts/lib/component/dataZoom/SelectZoomModel.js ● webpack ██████████████████ ███████ building modules (69%) 2108/2123 modules 15 ● webpack ██████████████████ ███████ building modules (69%) 2117/2127 modules 10 node_modules/react-router-redux/es/middleware.js ● webpack ██████████████████ ███████ building modules (69%) 2122/2130 modules 8 a ctive node_modules/react-router-redux/es/actions.js ● webpack ██████████████████ ███████ building modules (69%) 2143/2167 modules 24 node_modules/react-redux/es/connect/connect.js ● webpack ██████████████████ ███████ building modules (69%) 2185/2203 modules 18 node_modules/rc-dialog/es/DialogWrap.js ● webpack ██████████████████ ███████ building modules (69%) 2237/2265 modules 28 node_modules/antd/es/tag/index.js ● webpack ██████████████████ ███████ building modules (69%) 2283/2301 modules 18 babel-loader › src/pages/user/components/Filter.js ● webpack ██████████████████ ███████ building modules (69%) 2327/2338 modules 11 node_modules/rc-util/es/getScrollBarSize.js ● webpack ██████████████████ ███████ building modules (69%) 2339/2357 modules 18 node_modules/async-validator/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2347/2363 modules 16 node_modules/lodash/_createCurry.js ● webpack ██████████████████ ███████ building modules (69%) 2384/2400 modules 16 node_modules/rmc-feedback/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2402/2422 modules 20 node_modules/dom-align/es/getAlignOffset.js ● webpack ██████████████████ ███████ building modules (69%) 2426/2438 modules 12 node_modules/dva-core/lib/prefixType.js ● webpack ██████████████████ ███████ building modules (69%) 2430/2438 modules 8 a ● webpack ██████████████████ ███████ building modules (69%) 2451/2471 modules 20 node_modules/antd/es/badge/index.js ● webpack ██████████████████ ███████ building modules (69%) 2485/2494 modules 9 a node_modules/fbjs/lib/emptyObject.js ● webpack ██████████████████ ███████ building modules (69%) 2490/2520 modules 30 node_modules/antd/es/date-picker/WeekPicker.js ● webpack ██████████████████ ███████ building modules (69%) 2512/2524 modules 12 node_modules/react-side-effect/lib/index.js ● webpack ██████████████████ ███████ building modules (69%) 2519/2529 modules 10 node_modules/redux-saga/lib/internal/runSaga.js ● webpack ██████████████████ ███████ building modules (69%) 2524/2532 modules 8 a node_modules/antd/es/layout/Sider.js ● webpack ██████████████████ ███████ building modules (69%) 2540/2549 modules 9 a node_modules/antd/es/_util/getDataOrAriaProps.js ● webpack ██████████████████ ███████ building modules (69%) 2554/2561 modules 7 a node_modules/redux-saga/lib/internal/sagaHelpers/index.js ● webpack ██████████████████ ███████ building modules (69%) 2577/2581 modules 4 a node_modules/rc-switch/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2581/2589 modules 8 a node_modules/rc-drawer/es/index.js ● webpack ██████████████████ ███████ building modules (69%) 2604/2610 modules 6 a node_modules/rc-calendar/es/date/DateInput.js ● webpack ██████████████████ ███████ finish module graph (70%) ● webpack ███████████████████ ██████ optimizing (73%) ● webpack ████████████████████ █████ advanced chunk modules optimization (79%) ● webpack ██████████████████████ ███ after hashing (88%) ● webpack ███████████████████████ ██ additional chunk assets processing (90%) ● webpack ███████████████████████ ██ chunk asset optimization (92%) UglifyJSPlugi n ● webpack ███████████████████████ ██ after chunk asset optimization (92%) ➜ antd-admin git:( master ) ✗ npm run build > antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin > umi build [12:40:22] webpack compiled in 15s 56ms DONE Compiled successfully in 15062ms 12:40:22 File sizes after gzip: 887 B dist/ p__dashboard__model.js.async.js 877 B dist/ precache-manifest.6d2a703c437c4f28b5f6e072817bbe6e.js 786 B dist/ p__UIElement__editor__index.async.js 764 B dist/ i18n-0.async.js 652 B dist/ p__login__index.chunk.css 614 B dist/ p__user__$id__models__detail.js.async.js 599 B dist/ p__post__model.js.async.js 589 B dist/ p__user__$id__index.async.js 569 B dist/ service-worker.js 530 B dist/ p__request__index.chunk.css 515 B dist/ p__login__model.js.async.js 419 B dist/ p__index.async.js 245 B dist/ p__chart__ECharts__index.chunk.css 230 B dist/ p__chart__Recharts__index.chunk.css 138 B dist/ p__user__$id__index.chunk.css 74 B dist/ p__chart__highCharts__index.chunk.css 66 B dist/ p__user__index.chunk.css 66 B dist/ p__post__index.chunk.css The bundle size is significantly larger than recommended. Consider reducing it with code splitting: https://goo.gl/9VhYWB You can also analyze the project dependencies: https://goo.gl/LeUzfb ➜ antd-admin git:( master ) ✗
- 0:00/0:00
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/_media/term_js_i18n.svg b/docs/_media/term_js_i18n.svg
deleted file mode 100644
index f985604..0000000
--- a/docs/_media/term_js_i18n.svg
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ➜ antd-admin git:( master ) ✗ ➜ antd-admin git:( master ) ✗ n ➜ antd-admin git:( master ) ✗ np ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm ➜ antd-admin git:( master ) ✗ npm r ➜ antd-admin git:( master ) ✗ npm ru ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run ➜ antd-admin git:( master ) ✗ npm run a ➜ antd-admin git:( master ) ✗ npm run ad ➜ antd-admin git:( master ) ✗ npm run add ➜ antd-admin git:( master ) ✗ npm run add- ➜ antd-admin git:( master ) ✗ npm run add-l ➜ antd-admin git:( master ) ✗ npm run add-lo ➜ antd-admin git:( master ) ✗ npm run add-loc ➜ antd-admin git:( master ) ✗ npm run add-loca ➜ antd-admin git:( master ) ✗ npm run add-local ➜ antd-admin git:( master ) ✗ npm run add-locale ➜ antd-admin git:( master ) ✗ npm run add-locale ➜ antd-admin git:( master ) ✗ npm run add-locale j ➜ antd-admin git:( master ) ✗ npm run add-locale ja ➜ antd-admin git:( master ) ✗ npm run e ➜ antd-admin git:( master ) ✗ npm run ex ➜ antd-admin git:( master ) ✗ npm run ext ➜ antd-admin git:( master ) ✗ npm run extr ➜ antd-admin git:( master ) ✗ npm run extra ➜ antd-admin git:( master ) ✗ npm run extrac ➜ antd-admin git:( master ) ✗ npm run extract ➜ antd-admin git:( master ) ✗ npm run add-locale ja > antd-admin@5.0.0-beta add-locale /Users/zuiidea/web/antd-admin > lingui add-locale "ja" Added locale ja . (use " lingui extract " to extract messages) ➜ antd-admin git:( master ) ✗ npm run extract > antd-admin@5.0.0-beta extract /Users/zuiidea/web/antd-admin > lingui extract ➜ antd-admin git:( master ) ✗ npm run t ➜ antd-admin git:( master ) ✗ npm run tr ➜ antd-admin git:( master ) ✗ npm run tra ➜ antd-admin git:( master ) ✗ npm run tran ➜ antd-admin git:( master ) ✗ npm run trans ➜ antd-admin git:( master ) ✗ npm run trans: ➜ antd-admin git:( master ) ✗ npm run trans:o ➜ antd-admin git:( master ) ✗ npm run trans:on ➜ antd-admin git:( master ) ✗ npm run trans:onl Catalog statistics: ┌─────────────┬─────────────┬─────────┐ │ Language │ Total count │ Missing │ ├─────────────┼─────────────┼─────────┤ │ en (source) │ 52 │ - │ │ ja │ 52 │ 52 │ │ zh │ 52 │ 0 │ └─────────────┴─────────────┴─────────┘ (use " lingui add-locale <language> " to add more locales) (use " lingui extract " to update catalogs with new messages) (use " lingui compile " to compile catalogs for production) ➜ antd-admin git:( master ) ✗ npm run trans:only ➜ antd-admin git:( master ) ✗ npm run trans:only > antd-admin@5.0.0-beta trans:only /Users/zuiidea/web/antd-admin > node ./scripts/translate.js add to skip: Publish Date add to skip: Reset add to skip: Search add to skip: Search Name add to skip: Send add to skip: Sign in add to skip: Sign out add to skip: Switch Theme add to skip: Tags add to skip: The input is not valid E-mail! add to skip: The input is not valid phone! add to skip: Title add to skip: Total {total} Items add to skip: Unpublished add to skip: Update add to skip: Update User add to skip: Username add to skip: Views add to skip: Visibility add to skip: You have viewed all notifications. completed: en -> zh start: en -> ja link: en -> ja: /dashboard -> /ja/dashboard youdao: en -> ja: Add Param -> Param の追加 youdao: en -> ja: Address -> アドレス youdao: en -> ja: Age -> 年齢 youdao: en -> ja: Are you sure delete this record? -> このレコードを削除してもよ ろしいですか? youdao: en -> ja: Author -> 著者 youdao: en -> ja: Avatar -> アバター youdao: en -> ja: Categories -> カテゴリ youdao: en -> ja: Clear notifications -> 通知のクリア youdao: en -> ja: Comments -> コメント youdao: en -> ja: Create -> 作成 youdao: en -> ja: Create User -> ユーザーの作成 same: en -> ja: CreateTime youdao: en -> ja: Dark -> 暗い youdao: en -> ja: Delete -> 削除 youdao: en -> ja: Email -> 電子メール youdao: en -> ja: Female -> 女性 youdao: en -> ja: Gender -> 性別 youdao: en -> ja: Hi, -> こんにちは youdao: en -> ja: Image -> イメージ youdao: en -> ja: Light -> 光 youdao: en -> ja: Male -> 男性 youdao: en -> ja: Name -> 名前 youdao: en -> ja: NickName -> ニックネーム youdao: en -> ja: Not Found -> 見つからない youdao: en -> ja: Operation -> 操作 same: en -> ja: Params youdao: en -> ja: Password -> パスワード youdao: en -> ja: Phone -> 電話 youdao: en -> ja: Pick an address -> 住所の選択 youdao: en -> ja: Please pick an address -> 住所を選択してください same: en -> ja: Publised youdao: en -> ja: Publish Date -> 発行日 youdao: en -> ja: Reset -> リセット youdao: en -> ja: Search -> 検索 youdao: en -> ja: Search Name -> 検索名 youdao: en -> ja: Send -> 送信 youdao: en -> ja: Sign in -> サインイン youdao: en -> ja: Sign out -> サインアウト youdao: en -> ja: Switch Theme -> テーマを切り替える youdao: en -> ja: Tags -> タグ youdao: en -> ja: The input is not valid E-mail! -> 入力が有効な電子メールではあ りません! youdao: en -> ja: The input is not valid phone! -> 入力は有効な電話ではありません ! youdao: en -> ja: Title -> タイトル youdao: en -> ja: Total {total} Items -> 合計{total}項目 youdao: en -> ja: Unpublished -> 未発表 youdao: en -> ja: Update -> 更新 youdao: en -> ja: Update User -> ユーザーの更新 youdao: en -> ja: Username -> 名 youdao: en -> ja: Views -> 表示モード youdao: en -> ja: Visibility -> 可視 性 youdao: en -> ja: You have viewed all notifications. -> すべての通知を表示しまし た。 completed: en -> ja All translations have been completed. ➜ antd-admin git:( master ) ✗
- 0:00/0:00
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
deleted file mode 100644
index 3d6d176..0000000
--- a/docs/_sidebar.md
+++ /dev/null
@@ -1,12 +0,0 @@
-- Getting started
- - [Quick Start](getting-started.md)
-- Customization
- - [Configuration](configuration.md)
- - [API Configuration](API-configuration.md)
- - [I18n](i18n.md)
- - [Layout](layout.md)
- - [Request](request.md)
-- Guide
- - [Deploy](deploy.md)
-- [Change Log](change-log.md)
-- [FAQ](faq.md)
diff --git a/docs/change-log.md b/docs/change-log.md
deleted file mode 100644
index 3465321..0000000
--- a/docs/change-log.md
+++ /dev/null
@@ -1,50 +0,0 @@
-## 5.0.0
-
-#### Optimization
-
-- Try to use decorators to simplify code writing and improve code readability.
-
-- API configurization to simplify the way data is obtained.
-
-- The files in `utils` are split and each has its own role.
-
-- Simplify the `utils/request` file without special handling.
-
-#### Specification
-
-- Functions add comments, parameters, return values, etc., ambiguous code adds comments, canonical reference [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html#appendices-jsdoc-tag-reference).
-
-- Semantic version number, specification participation [semantic version 2.0.0](https://semver.org/lang/zh-CN/).
-
-- Static code checking, unified code style, will use `prettier`, `stylelint`, `eslint` specification code before code submission.
-
-- Git submits information normalization, [git-commit-emoji-cn](https://github.com/liuchengxu/git-commit-emoji-cn).
-
-- Based on the pre-defined routing of `Umi`, there is no need to write a routing configuration file.
-
-- Use `React 16` new features such as `Fragment`, `Context`, `PureComponent`, etc.
-
-#### Features
-
-- Support internationalization, extract source fields from source code, load language packs on demand, and automatically translate online.
-
-- Support for the introduction `lodash` functions on demand.
-
-- Support multiple layouts, which rules can be used according to the rules.
-
-- Support Antd Admin to automatically compile and deploy on Travis.
-
-- Generate a documentation website using `Docsify`.
-
-
-#### Style
-
-- Added Antd Admin standalone Logo.
-
-- Rewrite the overall layout component, optimize the menu, automatic breadcrumb navigation, menu auto-expansion and other logic.
-
-- The mobile menu is changed to drawer.
-
-#### Other
-
-- Discard components such as `IconFont`, `Search`, `DataTable` because they are well supported and replaceable in `Antd`.
\ No newline at end of file
diff --git a/docs/configuration.md b/docs/configuration.md
deleted file mode 100644
index 54acff7..0000000
--- a/docs/configuration.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Configuration
-
-You can do some custom configuration in `/src/utils/config.js`:
-
-## siteName
-
-- Type `String`
-
- Configure the site name, apply it to the login box, and display the title text at the top of the sidebar.
-
-## copyright
-
-- Type: `String`
-
- Configure the copyright notice to apply to the login page, at the bottom of the `Primary` layout.
-
-## logoPath
-
-- Type: `String`
-
- Configure the site logo to apply to the login box and the Logo display at the top of the sidebar.
-
-## apiPrefix
-
-- Type: `String`
-
- Configure the prefix of the interface in the project. The interface related documents can be viewed [API configuration](API-configuration.md)
-
-## fixedHeader
-
-- Type: `String`
-
- Under the `Primary` layout, whether the top of the page is fixed when scrolling。
-
-## layouts
-
-- Type: `Array`
-
- Configuration? Which routes use which layout, unspecified route uses the default layout `Public`, the project currently has `Primary` and `Public` layouts,
- The default configuration is as follows:
-
- ```javascript
- layouts: [
- {
- name: 'primary',
- include: [/.*/],
- exclude: [/(\/(en|zh))*\/login/],
- },
- ],
- ```
-
- The object properties for each layout are as follows:
-
- - `name` - The name of the layout;
-
- - `include` - Specifies a list of routing rules that use this layout, which can be a regular expression or a string;
-
- - `exclude` - Specifies a list of routing rules that do not use this layout, which can be a regular expression or a string.
-
- > Note: `exclude` takes precedence over `include`, and the layout previous it has a higher priority than the behind layout. The development process may need to be combined with the layout in the `src/layouts` directory. For details, see [Using Layout](./layout.md).
-
-## i18n
-
-- Type: `Object`
-
- Configure internationalization, the default configuration is as follows:
-
- ```javascript
- i18n: {
- languages: [
- {
- key: 'en',
- title: 'English',
- flag: '/america.svg',
- },
- {
- key: 'zh',
- title: '中文',
- flag: '/china.svg',
- },
- ],
- defaultLanguage: 'en',
- }
- ```
-
- ### i18n.languages
-
- - Type: `Array`
-
- Specify which languages the app supports, and the object properties for each language are as follows:
-
- - `key` - The `key` of the language is applied to the page url to distinguish the language, and also corresponds to the language package folder name in the `src/locales` directory;
-
- - `title` - The name of the language, at the bottom of the login page, at the top of the `Primary` layout, the language switch is displayed;
-
- - `flag` - The path of the flag icon of the language, the language switching display at the top of the `Primary` layout.
-
- ### i18n.defaultLanguage
-
- - Type: `String`
-
- Configure the default language.
diff --git a/docs/deploy.md b/docs/deploy.md
deleted file mode 100644
index 3cc157f..0000000
--- a/docs/deploy.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Deploy
-
-After the development is completed and verified in the development environment, it needs to be deployed to our users.
-
-
-
-## Build
-
-First execute the following command,
-
-```bash
-npm run build
-```
-
-After a few seconds, the output should look like this:
-
-```bash
-> antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin
-> umi build
-
-[21:13:17] webpack compiled in 43s 868ms
- DONE Compiled successfully in 43877ms 21:13:17
-
-File sizes after gzip:
-
- 1.3 MB dist/vendors.async.js
- 308.21 KB dist/umi.js
- 45.49 KB dist/vendors.chunk.css
- 36.08 KB dist/p__chart__highCharts__index.async.js
- 33.53 KB dist/p__user__index.async.js
- 22.36 KB dist/p__chart__ECharts__index.async.js
- 4.21 KB dist/p__dashboard__index.async.js
- 4.06 KB dist/umi.css
- ...
-```
-
-The `build` command will package all resources, including JavaScript, CSS, web fonts, images, html, and more. You can find these files in the `dist/` directory.
-
-> If you have requirements for using HashHistory , deploying html to non-root directories, statics, etc., check out [Umi Deployment] (https://umijs.org/en/guide/deploy.html).
-
-## Local verification
-
-
-Local verification can be done via `serve` before publishing.
-
-```
-$ yarn global add serve
-$ serve ./dist
-
-Serving!
-
-- Local: http://localhost:5000
-- On Your Network: http://{Your IP}:5000
-
-Copied local address to clipboard!
-
-```
-
-Access [http://localhost:5000](http://localhost:5000), under normal circumstances, it should be consistent with `npm start` (The API may not get the correct data).
-
-
-## Deploy
-
-Next, we can upload the static file to the server. If you use Nginx as the Web server, you can configure it in `ngnix.conf`:
-```
-server
- {
- listen 80;
- # Specify an accessible domain name
- server_name antd-admin.zuiidea.com;
- # The directory where the compiled files are stored
- root /home/www/antd-admin/dist;
-
- # Proxy server interface to avoid cross-domain
- location /api {
- proxy_pass http://localhost:7000/api;
- }
-
- Because the front end uses BrowserHistory, it will route backback to index.html
- location / {
- index index.html;
- try_files $uri $uri/ /index.html;
- }
- }
-```
-
-Restart the web server and access [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) , You will see the correct page.
-
-```bash
-nginx -s reload
-```
-
-Similarly, if you use Caddy as a web server, you can do this in `Caddyfile`:
-
-```
-antd-admin.zuiidea.com {
- gzip
- root /home/www/antd-admin/dist
- proxy /api http://localhost:7000
-
- rewrite {
- if {path} not_match ^/api
- to {path} {path}/ /
- }
-}
-
-
-antd-admin.zuiidea.com/public {
- gzip
- root /home/www/antd-admin/dist/static/public
-}
-
-```
diff --git a/docs/faq.md b/docs/faq.md
deleted file mode 100644
index 2c6409d..0000000
--- a/docs/faq.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# FAQ
-
-Most asked
-
-## create new page
-
- 1. just copy a page in /src/pages (route here auto generated by [umi](https://umijs.org/guide/router.html#basic-routing))
- 2. modify namespace/pathToRegexp in model.js
- 3. modify mock route.js to add a route
\ No newline at end of file
diff --git a/docs/getting-started.md b/docs/getting-started.md
deleted file mode 100644
index 9a9c90f..0000000
--- a/docs/getting-started.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Quick Start
-
-> Before delving into Ant Design React, a good knowledge base of [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn) . Learn about [UmiJS](https://umijs.org/) , [Dva](http://github.com/dvajs/dva) . And properly installed and configured [Node.js](https://nodejs.org/) v8 or above, [Git](https://git-scm.com/). It would be helpful if you have pre-existing knowledge on those.
-
-## Installation
-
-```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
-```
-
-## Scaffolding
-
-The project layout is as follows:
-
-```bash
-├── dist/ # Default build output directory
-├── mock/ # Mock files
-├── public/ # Static resource
-├── src/ # Source code
-│ ├── components/ # Components
-│ ├── e2e/ # Integrated Test Case
-│ ├── layouts/ # Common Layouts
-│ ├── locales/ # i18n resources
-│ ├── models/ # Global dva Model
-│ ├── pages/ # Sub-pages and templates
-│ ├── services/ # Backend Services
-│ │ ├── api.js # API configuration
-│ │ └── index.js # API export
-│ ├── themes/ # Themes
-│ │ ├── default.less # Less variable
-│ │ ├── index.less # Global style
-│ │ ├── mixin.less # Less mixin
-│ │ └── vars.less # Less variable and mixin
-│ ├── utils/ # Utility
-│ │ ├── config.js # Application configuration
-│ │ ├── constant.js # Static constant
-│ │ ├── index.js # Utility methods
-│ │ ├── request.js # Request function(axios)
-│ │ └── theme.js # Style variables used in js
-├── .editorconfig
-├── .env
-├── .eslintrc
-├── .gitignore
-├── .prettierignore
-├── .prettierrc
-├── .stylelintrc.json
-├── .travis.yml
-└── .umirc.js
-└── package.json
-```
-
-## Development
-
-1. Install Dependencies.
-
-```bash
-yarn install
-```
-
-Or
-
-```bash
-npm install
-```
-
-2. Start local server.
-
-```bash
-npm run start
-```
-
-3. After the startup is complete, open a browser and visit [http://localhost:7000](http://localhost:7000), If you need to change the startup port, you can configure it in the `.env` file.
diff --git a/docs/i18n.md b/docs/i18n.md
deleted file mode 100644
index 33bf011..0000000
--- a/docs/i18n.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# globalization
-
-## Add language
-
-Take Japanese as an example.
-
-
-
-1. Add a language pack local file, `ja` is the Japanese language code, and a list of languages that support translation [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05), the `src/locales/ja/messages.json` file will be generated after running the following command.
-
- ```bash
- npm run add-locale ja
- ```
-
-2. Extract the fields in the code that need to be translated, ie `?message `, `` t`message `` in the `message` field, run the following command after `src/locales/ja /messages.json` will appear after the extracted field configuration.
-
- ```bash
- npm run extract
- ```
-
- You will see the following information:
-
- ```bash
- Catalog statistics:
- ┌─────────────┬─────────────┬─────────┐
- │ Language │ Total count │ Missing │
- ├─────────────┼─────────────┼─────────┤
- │ en (source) │ 52 │ - │
- │ ja │ 52 │ 52 │
- │ zh │ 52 │ 0 │
- └─────────────┴─────────────┴─────────┘
- ```
-
-3. At the same time, we have added the relevant configuration in `src/utils/config.js`.
-
- ```javascript
- {
- ...
- i18n: {
- languages: [
- ...
- {
- key:'ja',
- title: '日本語',
- flag: '/japanese.svg',
- },
- ],
- },
- }
- ```
-
- > Routing related effects, after the configuration `npm run start` takes effect after restart.
-
-4. Use the built-in commands for automatic translation. You will see the translated configuration in `src/locales/ja/messages.json`.
-
- ```bash
- npm run trans:only
- ```
-
- You will see the following information:
-
- ```bash
- start: en -> ja
- ...
- youdao: en -> ja: Unpublished -> 未発表
- youdao: en -> ja: Update -> 更新
- youdao: en -> ja: Update User -> ユーザーの更新
- youdao: en -> ja: Username -> 名
- ...
- All translations have been completed.
- ```
-
- > `npm run trans` will execute `npm run extract` and `npm run trans:only` in order.
-
-5. Finally, you can adjust the inaccurate fields in `src/locales/ja/messages.json`. Start the development mode `npm run start`, open [http://localhost:7000/ja/login](http://localhost:7000/ja/login) and you will see the Japanese version of the app.
diff --git a/docs/index.html b/docs/index.html
deleted file mode 100644
index e981d4c..0000000
--- a/docs/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
- antd-admin - An admin dashboard application demo built upon Ant Design and UmiJS
-
-
-
-
-
-
-
-
-
- En
- 中文
-
- Loading...
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/layout.md b/docs/layout.md
deleted file mode 100644
index 22223ad..0000000
--- a/docs/layout.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Layout
-
-## Add a new layout
-
-Take a new layout named `secondary` as an example to make the route starting with `secondary` use this layout.
-
-1. Add related configuration in `src/utils/config.js`. For details, please refer to [layouts](/configuration?id=layouts).
-
-```javascript
- layouts: [
- {
- name: 'primary',
- include: [/.*/],
- exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/],
- },
- {
- name: 'secondary',
- include: [/(\/(en|zh))*\/secondary\/(.*)/],
- },
- ],
-```
-
-2. Add the `secondary` layout component to the `src/layouts/BaseLayout.js` file.
-
-```javascript
- import SecondaryLayout from './SecondaryLayout'
-
- const LayoutMap = {
- primary: PrimaryLayout,
- public: PublicLayout,
- secondary: SecondaryLayout,
- }
-```
-
-3. Add the `SecondaryLayout.js` file to the `src/layouts/` directory.
-
-```javascript
- import React from 'react'
-
- export default ({ children }) => {
- return (
-
-
Secondary
- {children}
-
- )
- }
-```
-
-4. Add a `secondary/index.js` file to the `src/pages/` directory.
-
-```javascript
- import React from 'react'
-
- export default ({ children }) => {
- Return Secondary page Content
- }
-```
-
-5. Finally, start the development mode `npm run start`, open [http://localhost:7000/secondary/](http://localhost:7000/secondary/) and you will see the page for the `secondary` layout.
diff --git a/docs/request.md b/docs/request.md
deleted file mode 100644
index 69d6607..0000000
--- a/docs/request.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# HTTP request
-
-this project use axios for http service, file located in src/utils/request.js
-
-## Custom Header
-
-As for privilege access or modify cookie, you could add header param by yourself
-
-```
-axios.defaults.headers.common['Authorization'] = 'token'
-```
-
-Or
-
-```
-axios.interceptors.request.use(function (config) {
- config.headers.token = window.localStorage.getItem('token');
- return config;
-}, function (error) {
- return Promise.reject(error);
-});
-```
\ No newline at end of file
diff --git a/docs/sw.js b/docs/sw.js
deleted file mode 100644
index cf6295c..0000000
--- a/docs/sw.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* ===========================================================
- * docsify sw.js
- * ===========================================================
- * Copyright 2016 @huxpro
- * Licensed under Apache 2.0
- * Register service worker.
- * ========================================================== */
-
-const RUNTIME = 'docsify'
-const HOSTNAME_WHITELIST = [
- self.location.hostname,
- 'fonts.gstatic.com',
- 'fonts.googleapis.com',
- 'unpkg.com'
-]
-
-// The Util Function to hack URLs of intercepted requests
-const getFixedUrl = (req) => {
- var now = Date.now()
- var url = new URL(req.url)
-
- // 1. fixed http URL
- // Just keep syncing with location.protocol
- // fetch(httpURL) belongs to active mixed content.
- // And fetch(httpRequest) is not supported yet.
- url.protocol = self.location.protocol
-
- // 2. add query for caching-busting.
- // Github Pages served with Cache-Control: max-age=600
- // max-age on mutable content is error-prone, with SW life of bugs can even extend.
- // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
- // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
- if (url.hostname === self.location.hostname) {
- url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
- }
- return url.href
-}
-
-/**
- * @Lifecycle Activate
- * New one activated when old isnt being used.
- *
- * waitUntil(): activating ====> activated
- */
-self.addEventListener('activate', event => {
- event.waitUntil(self.clients.claim())
-})
-
-/**
- * @Functional Fetch
- * All network requests are being intercepted here.
- *
- * void respondWith(Promise r)
- */
-self.addEventListener('fetch', event => {
- // Skip some of cross-origin requests, like those for Google Analytics.
- if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
- // Stale-while-revalidate
- // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
- // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
- const cached = caches.match(event.request)
- const fixedUrl = getFixedUrl(event.request)
- const fetched = fetch(fixedUrl, { cache: 'no-store' })
- const fetchedCopy = fetched.then(resp => resp.clone())
-
- // Call respondWith() with whatever we get first.
- // If the fetch fails (e.g disconnected), wait for the cache.
- // If there’s nothing in cache, wait for the fetch.
- // If neither yields a response, return offline pages.
- event.respondWith(
- Promise.race([fetched.catch(_ => cached), cached])
- .then(resp => resp || fetched)
- .catch(_ => { /* eat any errors */ })
- )
-
- // Update the cache with the version we fetched (only for ok status)
- event.waitUntil(
- Promise.all([fetchedCopy, caches.open(RUNTIME)])
- .then(([response, cache]) => response.ok && cache.put(event.request, response))
- .catch(_ => { /* eat any errors */ })
- )
- }
-})
\ No newline at end of file
diff --git a/docs/zh-cn/API-configuration.md b/docs/zh-cn/API-configuration.md
deleted file mode 100644
index 19913ac..0000000
--- a/docs/zh-cn/API-configuration.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# 接口配置
-
-## 为什么
-
-在使用了`redux`或者`dva`项目中,我们经常会写类似下面的`service`层的函数,使代码结构更清晰,但是很容易看出,我们会写很多相似的代码,在`antd-admin@5.0`中,使用了更加简洁的配置方式实现了相同的功能。
-
-```javascript
-export async function login(data) {
- return request({
- url: '/api/v1/user/login',
- method: 'post',
- data,
- })
-}
-```
-
-## 配置和使用
-
-在`src/services/api.js`文件中,你会看到如下配置对象,对象的键用于调用时的函数名称,对象的值为请求的`url`,默认请求方式为`GET`,如果是其他请求方式对象的值的格式则为`'method url'`。
-
-```javascript
-export default {
- ...
- queryUser: '/user/:id',
- queryUserList: '/users',
- updateUser: 'Patch /user/:id',
- createUser: 'POST /user/:id',
- removeUser: 'DELETE /user/:id',
- removeUserList: 'POST /users/delete',
- ...
-}
-```
-
-在其他文件中使用
-
-```javascript
-import { queryUser } from 'api'
-
-// 一般文件中
-...
-queryUser(option).then(data => console.log(data))
-...
-
-// model文件中
-...
-yield call(queryUser, option)
-...
-```
-
-## 实现方式
-
-参考`src/services/index.js`文件,对api配置进行遍历,每个属性都返回对应的封装后的request函数。
-
-```javascript
-import request from 'utils/request'
-import { apiPrefix } from 'utils/config'
-
-import api from './api'
-
-const gen = params => {
- let url = apiPrefix + params
- let method = 'GET'
-
- const paramsArray = params.split(' ')
- if (paramsArray.length === 2) {
- method = paramsArray[0]
- url = apiPrefix + paramsArray[1]
- }
-
- return function(data) {
- return request({
- url,
- data,
- method,
- })
- }
-}
-
-const APIFunction = {}
-for (const key in api) {
- APIFunction[key] = gen(api[key])
-}
-
-module.exports = APIFunction
-
-```
\ No newline at end of file
diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md
deleted file mode 100644
index 61799bb..0000000
--- a/docs/zh-cn/README.md
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-
-AntD Admin
-
-
-
-一套优秀的中后台前端解决方案
-
-[](https://github.com/ant-design/ant-design)
-[](https://github.com/umijs/umi)
-[](https://github.com/zuiidea/antd-admin/issues)
-[](http://opensource.org/licenses/MIT)
-
-[](https://github.com/zuiidea/antd-admin/pulls)
-[](https://gitter.im/antd-admin/antd-admin)
-
-
-
-- 在线演示 - [https://antd-admin.zuiidea.com](https://antd-admin.zuiidea.com)
-- 使用文档 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/](https://doc.antd-admin.zuiidea.com/#/zh-cn/)
-- 常见问题 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/faq](https://doc.antd-admin.zuiidea.com/#/zh-cn/faq)
-- 更新日志 - [https://doc.antd-admin.zuiidea.com/#/zh-cn/change-log](https://doc.antd-admin.zuiidea.com/#/zh-cn/change-log)
-
-## 特性
-
-- 国际化,源码中抽离翻译字段,按需加载语言包
-- 动态权限,不同权限对应不同菜单
-- 优雅美观,Ant Design 设计体系
-- Mock 数据,本地数据调试
-
-
-## 使用
-
-1. 下载项目代码。
-
-```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
-```
-
-2. 进入目录安装依赖,国内用户推荐使用 [cnpm](https://cnpmjs.org) 进行加速。
-
-```bash
-yarn install
-```
-
-或者
-
-```bash
-npm install
-```
-
-3. 启动本地服务器。
-
-```bash
-npm run start
-```
-
-4. 启动完成后打开浏览器访问 [http://localhost:7000](http://localhost:7000),如果需要更改启动端口,可在 `.env` 文件中配置。
-
-
-> 更多信息请参考 [使用文档](https://doc.antd-admin.zuiidea.com/#/zh-cn/)。
-
-
-## 支持环境
-
-现代浏览器。
-
-| [ ](http://godban.github.io/browsers-support-badges/)IE / Edge | [ ](http://godban.github.io/browsers-support-badges/)Firefox | [ ](http://godban.github.io/browsers-support-badges/)Chrome | [ ](http://godban.github.io/browsers-support-badges/)Safari | [ ](http://godban.github.io/browsers-support-badges/)Opera |
-| --------- | --------- | --------- | --------- | --------- |
-|IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
-
-## 参与贡献
-
-我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley:
-- 在你的公司或个人项目中使用 AntD Admin。
-- 通过 [Issue](http://github.com/zuiidea/antd-admin/issues) 报告 bug 或进行咨询。
-- 提交 [Pull Request](http://github.com/zuiidea/antd-admin/pulls) 改进代码。
-
-> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。
\ No newline at end of file
diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md
deleted file mode 100644
index 68dc92e..0000000
--- a/docs/zh-cn/_sidebar.md
+++ /dev/null
@@ -1,11 +0,0 @@
-- 入门
- - [快速上手](zh-cn/getting-started.md)
-- 定制化
- - [配置项](zh-cn/configuration.md)
- - [接口配置](zh-cn/API-configuration.md)
- - [国际化](zh-cn/i18n.md)
- - [布局](zh-cn/layout.md)
- - [http 请求](zh-cn/request.md)
-- 指南
- - [部署](zh-cn/deploy.md)
-- [更新日志](zh-cn/change-log.md)
diff --git a/docs/zh-cn/change-log.md b/docs/zh-cn/change-log.md
deleted file mode 100644
index 54b0ec6..0000000
--- a/docs/zh-cn/change-log.md
+++ /dev/null
@@ -1,52 +0,0 @@
-## 5.0.0
-
-#### 优化
-
-- 尽量使用修饰器,简化代码编写,提高代码可读性。
-
-- API 配置化,简化获取数据方式。
-
-- `utils` 内文件拆分,各司其职。
-
-- 简化`utils/request`文件,不做特殊处理。
-
-#### 规范
-
-- 函数添加描述、参数、返回值等注释,含糊不清的代码增加注释,规范参考 [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html#appendices-jsdoc-tag-reference)。
-
-- 语义化版本号,规范参加 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/)。
-
-- 静态代码检查,统一代码风格,代码提交前将会使用 `prettier`、`stylelint`、`eslint` 规范代码。
-
-- Git 提交信息规范化,[git-commit-emoji-cn](https://github.com/liuchengxu/git-commit-emoji-cn)。
-
-- 基于 `Umi` 的约定式路由,无需再写路由配置文件。
-
-- 使用 `React 16` 新特性,如 `Fragment`、`Context`、 `PureComponent`等。
-
-#### 功能
-
-- 支持国际化,源码中抽离翻译字段,按需加载语言包,自动在线翻译。
-
-- 支持按需引入 `lodash` 函数。
-
-- 支持多布局,可根据规则规定哪些路由使用哪种布局。
-
-- 支持 Antd Admin 在 Travis 上自动编译和部署。
-
-- 使用 `Docsify` 生成文档网站。
-
-
-#### 样式
-
-- 新增 Antd Admin 独立 Logo。
-
-- 重写整体布局组件,优化菜单、面包屑导航自动高亮,菜单自动展开等逻辑。
-
-- 移动端菜单更改为抽屉式。
-
-#### 其他
-
-- 废弃 `IconFont`、 `Search`、`DataTable`等组件,因为在 `Antd` 中有很好的支持和可替代的。
-
-
diff --git a/docs/zh-cn/configuration.md b/docs/zh-cn/configuration.md
deleted file mode 100644
index 70f517f..0000000
--- a/docs/zh-cn/configuration.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# 配置项
-
-你可以在 `/src/utils/config.js` 里做一些自定义配置:
-
-## siteName
-
-- 类型: `String`
-
- 配置站点名称,应用到登录框,侧边栏顶部的标题文字显示。
-
-## copyright
-
-- 类型: `String`
-
- 配置版权声明,应用到登录页、`Primay`布局底部。
-
-## logoPath
-
-- 类型: `String`
-
- 配置站点 Logo,应用到登录框,侧边栏顶部的 Logo 显示。
-
-## apiPrefix
-
-- 类型: `String`
-
- 配置项目中接口的前缀,接口相关文档可查看 [接口配置](API-configuration.md)
-
-## fixedHeader
-
-- 类型: `String`
-
- 在`Primary`布局下,页面滚动时是否固定顶部。
-
-## layouts
-
-- 类型: `Array`
-
- 配置哪些路由使用哪种布局,未指定路由使用默认布局 `Public`,项目中目前有 `Primary` 和 `Public` 两种布局,
- 默认配置如下:
-
- ```javascript
- layouts: [
- {
- name: 'primary',
- include: [/.*/],
- exclude: [/(\/(en|zh))*\/login/],
- },
- ],
- ```
-
- 每种布局的对象属性如下:
-
- - `name` - 布局的名称;
-
- - `include` - 指定使用该布局的路由规则列表,规则可为正则表达式或者字符串;
-
- - `exclude` - 指定不使用该布局的路由规则列表,规则可为正则表达式或者字符串。
-
- > 注意:`exclude` 优先级高于 `include`,前面的布局优先级高于后面的布局。开发过程中可能需要结合`src/layouts`目录下的布局使用,具体方法可查看 [使用布局](./layout.md)。
-
-## i18n
-
-- 类型: `Object`
-
- 配置国际化,默认配置如下:
-
- ```javascript
- i18n: {
- languages: [
- {
- key: 'en',
- title: 'English',
- flag: '/america.svg',
- },
- {
- key: 'zh',
- title: '中文',
- flag: '/china.svg',
- },
- ],
- defaultLanguage: 'en',
- }
- ```
-
- ### i18n.languages
-
- - 类型: `Array`
-
- 指定应用支持哪些语言,每种语言的对象属性如下:
-
- - `key` - 语言的`key`,应用到页面 url 上以区分语言,也对应 `src/locales` 目录下的语言包文件夹名;
-
- - `title` - 语言名称,在登录页底部、`Primay` 布局顶部语言切换显示;
-
- - `flag` - 语言的国旗图标的路径,在 `Primay` 布局顶部语言切换显示。
-
- ### i18n.defaultLanguage
-
- - 类型: `String`
-
- 配置默认语言。
diff --git a/docs/zh-cn/deploy.md b/docs/zh-cn/deploy.md
deleted file mode 100644
index cdbac06..0000000
--- a/docs/zh-cn/deploy.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# 部署
-
-完成开发并且在开发环境验证之后,就需要部署给我们的用户了。
-
-
-
-## 构建
-
-先执行下面的命令,
-
-```bash
-npm run build
-```
-
-几秒后,输出应该如下:
-
-```bash
-> antd-admin@5.0.0-beta build /Users/zuiidea/web/antd-admin
-> umi build
-
-[21:13:17] webpack compiled in 43s 868ms
- DONE Compiled successfully in 43877ms 21:13:17
-
-File sizes after gzip:
-
- 1.3 MB dist/vendors.async.js
- 308.21 KB dist/umi.js
- 45.49 KB dist/vendors.chunk.css
- 36.08 KB dist/p__chart__highCharts__index.async.js
- 33.53 KB dist/p__user__index.async.js
- 22.36 KB dist/p__chart__ECharts__index.async.js
- 4.21 KB dist/p__dashboard__index.async.js
- 4.06 KB dist/umi.css
- ...
-```
-
-`build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。你可以在 `dist/` 目录下找到这些文件。
-
-> 如果有使用 HashHistory 、 部署 html 到非根目录、静态化等需求,请查看[Umi 部署](https://umijs.org/zh/guide/deploy.html)。
-
-## 本地验证
-
-
-发布之前,可以通过 `serve` 做本地验证,
-
-```
-$ yarn global add serve
-$ serve ./dist
-
-Serving!
-
-- Local: http://localhost:5000
-- On Your Network: http://{Your IP}:5000
-
-Copied local address to clipboard!
-
-```
-
-访问 [http://localhost:5000](http://localhost:5000),正常情况下法应该是和 `npm start` 一致的(接口可能无法获取到正确数据)。
-
-
-## 部署
-
-接下来,我们可以把静态文件上传到服务器,如果使用 Nginx 作为 Web server,你可以在 `ngnix.conf` 中这样配置:
-
-```
-server
- {
- listen 80;
- # 指定可访问的域名
- server_name antd-admin.zuiidea.com;
- # 编译后的文件存放的目录
- root /home/www/antd-admin/dist;
-
- # 代理服务端接口,避免跨域
- location /api {
- proxy_pass http://localhost:7000/api;
- }
-
- # 因为前端使用了BrowserHistory,所以将路由 fallback 到 index.html
- location / {
- index index.html;
- try_files $uri $uri/ /index.html;
- }
- }
-```
-
-重启 Web server,访问 [http://antd-admin.zuiidea.com](http://antd-admin.zuiidea.com) ,你将看到正确的页面。
-
-```bash
-nginx -s reload
-```
-
-类似的,如果你使用 Caddy 作为 Web server,你可以在 `Caddyfile` 中这样配置:
-
-```
-antd-admin.zuiidea.com {
- gzip
- root /home/www/antd-admin/dist
- proxy /api http://localhost:7000
-
- rewrite {
- if {path} not_match ^/api
- to {path} {path}/ /
- }
-}
-
-
-antd-admin.zuiidea.com/public {
- gzip
- root /home/www/antd-admin/dist/static/public
-}
-
-```
diff --git a/docs/zh-cn/faq.md b/docs/zh-cn/faq.md
deleted file mode 100644
index bf5f1bd..0000000
--- a/docs/zh-cn/faq.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# 问题集锦
-
-## 新建页面
-
- 1. 直接从/src/pages复制一个page (会自动创建路由[umi](https://umijs.org/zh/guide/router.html#%E7%BA%A6%E5%AE%9A%E5%BC%8F%E8%B7%AF%E7%94%B1))
- 2. 修改 namespace/pathToRegexp 在 model.js
- 3. 修改 mock中route.js增加一条route
\ No newline at end of file
diff --git a/docs/zh-cn/getting-started.md b/docs/zh-cn/getting-started.md
deleted file mode 100644
index b9d1183..0000000
--- a/docs/zh-cn/getting-started.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# 快速上手
-
-> 在开始之前,推荐先学习 [React](http://facebook.github.io/react/) 、 [ES2015+](http://es6.ruanyifeng.com/) 、 [Antd Design](https://ant.design/docs/react/introduce-cn) , 了解 [UmiJS](https://umijs.org/) 、[Dva](http://github.com/dvajs/dva) ,并正确安装和配置了 [Node.js](https://nodejs.org/) v8 或以上 、[Git](https://git-scm.com/)。提前了解和学习这些知识会非常有帮助。
-
-## 安装
-
-```bash
-git clone https://github.com/zuiidea/antd-admin.git my-project
-cd my-project
-```
-
-## 目录结构
-
-应用的目录结构如下
-
-```bash
-├── dist/ # 默认build输出目录
-├── mock/ # Mock文件目录
-├── public/ # 静态资源文件目录
-├── src/ # 源码目录
-│ ├── components/ # 组件目录
-│ ├── e2e/ # e2e目录
-│ ├── layouts/ # 布局目录
-│ ├── locales/ # 国际化文件目录
-│ ├── models/ # 数据模型目录
-│ ├── pages/ # 页面组件目录
-│ ├── services/ # 数据接口目录
-│ │ ├── api.js # 接口配置
-│ │ └── index.js # 接口输出
-│ ├── themes/ # 项目样式目录
-│ │ ├── default.less # 样式变量
-│ │ ├── index.less # 全局样式
-│ │ ├── mixin.less # 样式函数
-│ │ └── vars.less # 样式变量及函数
-│ ├── utils/ # 工具函数目录
-│ │ ├── config.js # 项目配置
-│ │ ├── constant.js # 静态常量
-│ │ ├── index.js # 工具函数
-│ │ ├── request.js # 异步请求函数(axios)
-│ │ └── theme.js # 项目需要在js中使用到样式变量
-├── .editorconfig # 编辑器配置
-├── .env # 环境变量
-├── .eslintrc # ESlint配置
-├── .gitignore # Git忽略文件配置
-├── .prettierignore # Prettier忽略文件配置
-├── .prettierrc # Prettier配置
-├── .stylelintrc.json # Stylelint配置
-├── .travis.yml # Travis配置
-└── .umirc.js # Umi配置
-└── package.json # 项目信息
-```
-
-## 本地开发
-
-1. 进入目录安装依赖,国内用户推荐使用 [cnpm](https://cnpmjs.org) 进行加速
-
-```bash
-yarn install
-```
-
-或者
-
-```bash
-npm install
-```
-
-2. 启动本地服务器
-
-```bash
-npm run start
-```
-
-3. 启动完成后打开浏览器访问 [http://localhost:7000](http://localhost:7000),如果需要更改启动端口,可在 `.env` 文件中配置。
diff --git a/docs/zh-cn/i18n.md b/docs/zh-cn/i18n.md
deleted file mode 100644
index 33244d6..0000000
--- a/docs/zh-cn/i18n.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# 国际化
-
-## 新增应用语言
-
-以新增日语为例。
-
-
-
-1. 添加语言包本地文件,`ja` 为日语的语言代码,支持翻译的语言列表参考 [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05),运行下面命令后会生成 `src/locales/ja/messages.json` 文件。
-
- ```bash
- npm run add-locale ja
- ```
-
-2. 提取代码中需要翻译的字段,即 `message `、`` intl.formatMessage({ id: 'message `` 中 `message` 字段,运行下面命令后 `src/locales/ja/messages.json' }) 将会出现提取后的字段配置。
-
- ```bash
- npm run extract
- ```
-
- 你将看到如下信息:
-
- ```bash
- Catalog statistics:
- ┌─────────────┬─────────────┬─────────┐
- │ Language │ Total count │ Missing │
- ├─────────────┼─────────────┼─────────┤
- │ en (source) │ 52 │ - │
- │ ja │ 52 │ 52 │
- │ zh │ 52 │ 0 │
- └─────────────┴─────────────┴─────────┘
- ```
-
-3. 与此同时,我们在 `src/utils/config.js` 新增相关配置。
-
- ```javascript
- {
- ...
- i18n: {
- languages: [
- ...
- {
- key:'ja',
- title: '日本語',
- flag: '/japanese.svg',
- },
- ],
- },
- }
- ```
-
- > 路由相关效果,配置后 `npm run start` 重启后生效。
-
-4. 使用内置的命令进行自动翻译,在 `src/locales/ja/messages.json` 中将会看到翻译后的配置。
-
- ```bash
- npm run trans:only
- ```
-
- 你将看到如下信息:
-
- ```bash
- start: en -> ja
- ...
- youdao: en -> ja: Unpublished -> 未発表
- youdao: en -> ja: Update -> 更新
- youdao: en -> ja: Update User -> ユーザーの更新
- youdao: en -> ja: Username -> 名
- ...
- All translations have been completed.
- ```
-
- > `npm run trans` 将会依次执行 `npm run extract` 和 `npm run trans:only`
-
-5. 最后,可以在 `src/locales/ja/messages.json` 中对翻译不准确的的字段进行调整。启动开发模式 `npm run start`,打开 [http://localhost:7000/ja/login](http://localhost:7000/ja/login),你将看到日语版本的应用。
diff --git a/docs/zh-cn/layout.md b/docs/zh-cn/layout.md
deleted file mode 100644
index f3a39e7..0000000
--- a/docs/zh-cn/layout.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# 布局
-
-## 新增布局
-
-以新增名为 `secondary` 的布局为例,使以 `secondary` 开头的路由都使用该布局。
-
-1. 在 `src/utils/config.js` 新增相关配置,参数详细请查看 [layouts](/zh-cn/configuration?id=layouts)。
-
- ```javascript
- layouts: [
- {
- name: 'primary',
- include: [/.*/],
- exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/secondary\/(.*)/],
- },
- {
- name: 'secondary',
- include: [/(\/(en|zh))*\/secondary\/(.*)/],
- },
- ],
- ```
-
-2. 在`src/layouts/BaseLayout.js` 文件中新增 `secondary` 布局组件。
-
- ```javascript
- import SecondaryLayout from './SecondaryLayout'
-
- const LayoutMap = {
- primary: PrimaryLayout,
- public: PublicLayout,
- secondary: SecondaryLayout,
- }
- ```
-
-3. 在`src/layouts/` 目录中新增 `SecondaryLayout.js` 文件。
-
- ```javascript
- import React from 'react'
-
- export default ({ children }) => {
- return (
-
-
Secondary
- {children}
-
- )
- }
- ```
-
-4. 在`src/pages/` 目录中新增 `secondary/index.js` 文件。
-
- ```javascript
- import React from 'react'
-
- export default ({ children }) => {
- return Secondary page Content
- }
- ```
-
-5. 最后,启动开发模式 `npm run start`,打开 [http://localhost:7000/secondary/](http://localhost:7000/secondary/),你将看到 `secondary` 布局的页面。
diff --git a/docs/zh-cn/request.md b/docs/zh-cn/request.md
deleted file mode 100644
index a87022c..0000000
--- a/docs/zh-cn/request.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# HTTP请求
-
-本项目使用了axios提供http请求服务,文件在src/utils/request.js
-
-## 自定义Header
-
-为了提供鉴权、修改cookie等服务,可以手动修改Header
-
-```
-axios.defaults.headers.common['Authorization'] = 'token'
-```
-
-或者
-
-```
-// 添加请求拦截器
-axios.interceptors.request.use(function (config) {
- // 在发送请求之前做些什么
- config.headers.token = window.localStorage.getItem('token');
- return config;
-}, function (error) {
- return Promise.reject(error);
-});
-```
\ No newline at end of file
diff --git a/docs/zh-cn/router.md b/docs/zh-cn/router.md
deleted file mode 100644
index f511c2e..0000000
--- a/docs/zh-cn/router.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 路由
-
-本项目中采用约定式路由
-
-参考[umi 路由](https://umijs.org/zh/guide/router.html)
diff --git a/jest.config.js b/jest.config.js
deleted file mode 100644
index c52ac83..0000000
--- a/jest.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- testURL: 'http://localhost:8000',
-}
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 0000000..989eaee
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,15 @@
+import { Config, configUmiAlias, createConfig } from 'umi/test';
+
+export default async () => {
+ return (
+ (await configUmiAlias({
+ ...createConfig({
+ target: 'browser',
+ jsTransformer: 'esbuild',
+ jsTransformerOpts: { jsx: 'automatic' },
+ }),
+ // you can override umi default jest config here
+ setupFilesAfterEnv: ['/src/setupTests.ts'],
+ })) as Config.InitialOptions
+ );
+};
diff --git a/lingui.config.js b/lingui.config.js
deleted file mode 100644
index ecbd056..0000000
--- a/lingui.config.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { formatter } from "@lingui/format-json"
-
-export default {
- fallbackLocales: {
- default: "en",
- },
- sourceLocale: "en",
- locales: ["en", "zh", "pt-br"],
- catalogs: [
- {
- path: "src/locales/{locale}/messages",
- include: [
- "src/pages",
- "src/layouts",
- "src/components",
- "src/layouts",
- ],
- },
- ],
- format: formatter({ style: "minimal" }),
-}
diff --git a/llm/skills/admin.skill.ts b/llm/skills/admin.skill.ts
new file mode 100644
index 0000000..81b074b
--- /dev/null
+++ b/llm/skills/admin.skill.ts
@@ -0,0 +1,53 @@
+import * as adminSvc from '../../src/services/admin';
+
+/**
+ * Skill 元数据与封装 — Admin
+ * 目的:为自动化代理或 LLM 提供明确的能力描述与可直接调用的包装函数。
+ */
+export const meta = {
+ id: 'admin:manage',
+ name: 'Admin Management',
+ description: '登录/登出、获取当前管理员、管理管理员列表(增删改查)',
+ inputs: {
+ login: { adminname: 'string', password: 'string' },
+ getAdmin: { id: 'string' },
+ createAdmin: { values: 'object' },
+ updateAdmin: { id: 'string', values: 'object' },
+ deleteAdmin: { id: 'string' },
+ deleteAdmins: { ids: 'string[]' },
+ listAdmins: { params: 'object | undefined' },
+ },
+ outputs: 'JSON (see src/types/admin.ts for schema)',
+};
+
+export const actions = {
+ async login(payload: { adminname: string; password: string }) {
+ return adminSvc.login(payload);
+ },
+ async logout() {
+ return adminSvc.logout();
+ },
+ async currentAdmin() {
+ return adminSvc.currentAdmin();
+ },
+ async listAdmins(params?: Record) {
+ return adminSvc.listAdmins(params as any);
+ },
+ async createAdmin(values: Record) {
+ return adminSvc.createAdmin(values);
+ },
+ async getAdmin(id: string) {
+ return adminSvc.getAdmin(id);
+ },
+ async updateAdmin(id: string, values: Record) {
+ return adminSvc.updateAdmin(id, values);
+ },
+ async deleteAdmin(id: string) {
+ return adminSvc.deleteAdmin(id);
+ },
+ async deleteAdmins(ids: string[]) {
+ return adminSvc.deleteAdmins(ids);
+ },
+};
+
+export default { meta, actions };
diff --git a/llm/skills/dashboard.skill.ts b/llm/skills/dashboard.skill.ts
new file mode 100644
index 0000000..9b673e6
--- /dev/null
+++ b/llm/skills/dashboard.skill.ts
@@ -0,0 +1,17 @@
+import * as dashSvc from '../../src/services/dashboard';
+
+export const meta = {
+ id: 'dashboard:view',
+ name: 'Dashboard View',
+ description: '获取仪表盘汇总数据',
+ inputs: {},
+ outputs: 'JSON summary data from /dashboard',
+};
+
+export const actions = {
+ async getDashboard() {
+ return dashSvc.getDashboard();
+ },
+};
+
+export default { meta, actions };
diff --git a/llm/skills/index.ts b/llm/skills/index.ts
new file mode 100644
index 0000000..34cdb01
--- /dev/null
+++ b/llm/skills/index.ts
@@ -0,0 +1,32 @@
+/**
+ * Skills registry
+ *
+ * 目的:集中导出 llm/skills 下的 skill 模块,便于运行时自动发现或静态引入。
+ * 导出:
+ * - `skills`:按短名映射的 skill 模块集合。
+ * - `listSkills()`:返回所有 skill 的 meta 数组,便于展示或自动选择。
+ * - `getSkillById(id)`:基于 meta.id 查找对应 skill 模块。
+ */
+import admin from './admin.skill';
+import dashboard from './dashboard.skill';
+import menu from './menu.skill';
+import user from './user.skill';
+
+const skills: Record = {
+ admin,
+ menu,
+ user,
+ dashboard,
+};
+
+export function listSkills() {
+ return Object.values(skills).map((s) => s.meta);
+}
+
+export function getSkillById(id: string) {
+ return Object.values(skills).find((s) => s.meta && s.meta.id === id);
+}
+
+export { skills };
+
+export default skills;
diff --git a/llm/skills/menu.skill.ts b/llm/skills/menu.skill.ts
new file mode 100644
index 0000000..c542d96
--- /dev/null
+++ b/llm/skills/menu.skill.ts
@@ -0,0 +1,35 @@
+import * as menuSvc from '../../src/services/menu';
+
+export const meta = {
+ id: 'menu:manage',
+ name: 'Menu Management',
+ description: '列出、创建、更新、删除与排序菜单项',
+ inputs: {
+ listMenus: {},
+ createMenu: { values: 'MenuFormValues' },
+ updateMenu: { id: 'number | string', values: 'Partial' },
+ deleteMenu: { id: 'number | string' },
+ sortMenus: { order: 'Array' },
+ },
+ outputs: 'JSON (see src/types/menu.ts for schema)',
+};
+
+export const actions = {
+ async listMenus() {
+ return menuSvc.listMenus();
+ },
+ async createMenu(values: any) {
+ return menuSvc.createMenu(values);
+ },
+ async updateMenu(id: number | string, values: any) {
+ return menuSvc.updateMenu(id, values);
+ },
+ async deleteMenu(id: number | string) {
+ return menuSvc.deleteMenu(id);
+ },
+ async sortMenus(order: Array) {
+ return menuSvc.sortMenus(order);
+ },
+};
+
+export default { meta, actions };
diff --git a/llm/skills/user.skill.ts b/llm/skills/user.skill.ts
new file mode 100644
index 0000000..93a30b6
--- /dev/null
+++ b/llm/skills/user.skill.ts
@@ -0,0 +1,39 @@
+import * as userSvc from '../../src/services/user';
+
+export const meta = {
+ id: 'user:manage',
+ name: 'User Management',
+ description: '用户列表/分页、创建、获取、更新、删除与批量删除',
+ inputs: {
+ listUsers: { params: 'object | undefined' },
+ createUser: { values: 'object' },
+ getUser: { id: 'string' },
+ updateUser: { id: 'string', values: 'object' },
+ deleteUser: { id: 'string' },
+ deleteUsers: { ids: 'string[]' },
+ },
+ outputs: 'JSON (see src/types/user.ts for schema)',
+};
+
+export const actions = {
+ async listUsers(params?: Record) {
+ return userSvc.listUsers(params as any);
+ },
+ async createUser(values: Record) {
+ return userSvc.createUser(values);
+ },
+ async getUser(id: string) {
+ return userSvc.getUser(id);
+ },
+ async updateUser(id: string, values: Record) {
+ return userSvc.updateUser(id, values);
+ },
+ async deleteUser(id: string) {
+ return userSvc.deleteUser(id);
+ },
+ async deleteUsers(ids: string[]) {
+ return userSvc.deleteUsers(ids);
+ },
+};
+
+export default { meta, actions };
diff --git a/llms.txt b/llms.txt
new file mode 100644
index 0000000..7d78d9f
--- /dev/null
+++ b/llms.txt
@@ -0,0 +1,36 @@
+项目:antd-admin_next
+
+目标:将仓库中的关键信息写成对大模型(LLM)友好的简短说明,便于自动化理解、代码导航和提示生成。
+
+概览:
+- 技术栈:React + Ant Design + TypeScript(前端单页/多页面结构),源码位于 `src/`。
+- 路由/页面:页面文件放在 `src/pages/` 目录;布局在 `src/layouts/`;组件在 `src/components/`。
+- 重要工具:`src/utils/request.ts` 为 HTTP 请求封装,使用 `fetch`,默认 API 前缀由 `src/utils/config.ts` 提供。
+
+API 与约定:
+- 全局 API 前缀:`/api/v1`(见 `src/utils/config.ts`)。
+- 请求函数 `api(path, options)`:
+ - 自动把 `options.params` 转为查询字符串。
+ - 对对象 body 自动 JSON 序列化并设置 `Content-Type: application/json`(FormData 会被保留)。
+ - 当响应状态为 `204` 返回空对象;当 `res.ok` 为 false 时抛出 Error,错误信息优先使用响应体中的 `message` 字段。
+
+如何使用(对模型说明):
+- 若要调用后端资源,请调用 `api('/resource', { method: 'GET' | 'POST', params, body })`。
+- 成功响应应为 JSON,失败会抛出 Error。处理调用时应捕获异常并展示 `error.message`。
+
+本文件用途:
+- 作为 LLM 的快速上下文:当模型在代码中自动生成、补全或审阅代码时,可引用此文件以获得仓库结构、关键约定和请求行为的摘要。
+
+快速运行(可能依赖 package manager):
+- 常用:`npm run dev` 或 `pnpm dev` 启动开发服务器。
+
+如需更详细的接口契约或后端响应示例,请查看对应的 `src/services/` 目录下的 API 封装文件。
+
+Agent Skills:
+- 为方便自动化代理(Agent/LLM)调用,项目在 `llm/skills/` 下提供了若干能力描述与包装:
+ - `llm/skills/admin.skill.ts`:管理员管理相关操作(login、logout、list/create/update/delete)
+ - `llm/skills/menu.skill.ts`:菜单管理操作(list/create/update/delete/sort)
+ - `llm/skills/user.skill.ts`:用户管理操作(list/create/get/update/delete/batch)
+ - `llm/skills/dashboard.skill.ts`:获取仪表盘数据的读取操作
+
+这些 skill 文件导出 `meta`(能力描述)和 `actions`(可直接调用的异步函数),便于代理在运行时发现并调用对应后端逻辑。
diff --git a/mock/_utils.js b/mock/_utils.js
deleted file mode 100644
index 2229c12..0000000
--- a/mock/_utils.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * Query objects that specify keys and values in an array where all values are objects.
- * @param {array} array An array where all values are objects, like [{key:1},{key:2}].
- * @param {string} key The key of the object that needs to be queried.
- * @param {string} value The value of the object that needs to be queried.
- * @return {object|undefined} Return frist object when query success.
- */
-export function queryArray(array, key, value) {
- if (!Array.isArray(array)) {
- return
- }
- return array.filter(_ => _[key] === value)
-}
-
-export function randomNumber(min, max) {
- return Math.floor(Math.random() * (max - min) + min)
-}
-
-export function randomAvatar() {
- const avatarList = [
- 'photo-1549492864-2ec7d66ffb04.jpeg',
- 'photo-1480535339474-e083439a320d.jpeg',
- 'photo-1523419409543-a5e549c1faa8.jpeg',
- 'photo-1519648023493-d82b5f8d7b8a.jpeg',
- 'photo-1523307730650-594bc63f9d67.jpeg',
- 'photo-1522962506050-a2f0267e4895.jpeg',
- 'photo-1489779162738-f81aed9b0a25.jpeg',
- 'photo-1534308143481-c55f00be8bd7.jpeg',
- 'photo-1519336555923-59661f41bb45.jpeg',
- 'photo-1551438632-e8c7d9a5d1b7.jpeg',
- 'photo-1525879000488-bff3b1c387cf.jpeg',
- 'photo-1487412720507-e7ab37603c6f.jpeg',
- 'photo-1510227272981-87123e259b17.jpeg'
- ]
- return `//image.zuiidea.com/${avatarList[randomNumber(0, avatarList.length - 1)]}?imageView2/1/w/200/h/200/format/webp/q/75|imageslim`
-}
-
-export const Constant = {
- ApiPrefix: '/api/v1',
- NotFound: {
- message: 'Not Found',
- documentation_url: '',
- },
- Color: {
- green: '#64ea91',
- blue: '#8fc9fb',
- purple: '#d897eb',
- red: '#f69899',
- yellow: '#f8c82e',
- peach: '#f797d6',
- borderBase: '#e5e5e5',
- borderSplit: '#f4f4f4',
- grass: '#d6fbb5',
- sky: '#c1e0fc',
- },
-}
-
-export Mock from 'mockjs'
-export qs from 'qs'
diff --git a/mock/dashboard.js b/mock/dashboard.js
deleted file mode 100644
index 8758594..0000000
--- a/mock/dashboard.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { Mock, Constant } from './_utils'
-
-const { ApiPrefix, Color } = Constant
-
-const Dashboard = Mock.mock({
- 'sales|8': [
- {
- 'name|+1': 2008,
- 'Clothes|200-500': 1,
- 'Food|180-400': 1,
- 'Electronics|300-550': 1,
- },
- ],
- cpu: {
- 'usage|50-600': 1,
- space: 825,
- 'cpu|40-90': 1,
- 'data|20': [
- {
- 'cpu|20-80': 1,
- },
- ],
- },
- browser: [
- {
- name: 'Google Chrome',
- percent: 43.3,
- status: 1,
- },
- {
- name: 'Mozilla Firefox',
- percent: 33.4,
- status: 2,
- },
- {
- name: 'Apple Safari',
- percent: 34.6,
- status: 3,
- },
- {
- name: 'Internet Explorer',
- percent: 12.3,
- status: 4,
- },
- {
- name: 'Opera Mini',
- percent: 3.3,
- status: 1,
- },
- {
- name: 'Chromium',
- percent: 2.53,
- status: 1,
- },
- ],
- user: {
- name: 'github',
- sales: 3241,
- sold: 3556,
- },
- 'completed|12': [
- {
- 'name|+1': 2008,
- 'Task complete|200-1000': 1,
- 'Cards Complete|200-1000': 1,
- },
- ],
- 'comments|5': [
- {
- name: '@last',
- 'status|1-3': 1,
- content: '@sentence',
- avatar() {
- return Mock.Random.image(
- '48x48',
- Mock.Random.color(),
- '#757575',
- 'png',
- this.name.substr(0, 1)
- )
- },
- date() {
- return `2016-${Mock.Random.date('MM-dd')} ${Mock.Random.time(
- 'HH:mm:ss'
- )}`
- },
- },
- ],
- 'recentSales|36': [
- {
- 'id|+1': 1,
- name: '@last',
- 'status|1-4': 1,
- date() {
- return `${Mock.Random.integer(2015, 2016)}-${Mock.Random.date(
- 'MM-dd'
- )} ${Mock.Random.time('HH:mm:ss')}`
- },
- 'price|10-200.1-2': 1,
- },
- ],
- quote: {
- name: 'Joho Doe',
- title: 'Graphic Designer',
- content:
- "I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.",
- avatar:
- '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
- },
- numbers: [
- {
- icon: 'pay-circle-o',
- color: Color.green,
- title: 'Online Review',
- number: 2781,
- },
- {
- icon: 'team',
- color: Color.blue,
- title: 'New Customers',
- number: 3241,
- },
- {
- icon: 'message',
- color: Color.purple,
- title: 'Active Projects',
- number: 253,
- },
- {
- icon: 'shopping-cart',
- color: Color.red,
- title: 'Referrals',
- number: 4324,
- },
- ],
-})
-
-module.exports = {
- [`GET ${ApiPrefix}/dashboard`](req, res) {
- res.json(Dashboard)
- },
-}
diff --git a/mock/index.js b/mock/index.js
new file mode 100644
index 0000000..372bbd5
--- /dev/null
+++ b/mock/index.js
@@ -0,0 +1,373 @@
+// Consolidated mock server for development (simplified)
+const Mock = require('mockjs');
+const qs = require('qs');
+
+const ApiPrefix = '/api/v1';
+
+// simple in-memory admin users with richer mock fields
+const adminList = [
+ {
+ id: 1,
+ username: 'admin',
+ password: 'admin',
+ real_name: '李四',
+ nick_name: '超级管理员',
+ mobile: '13800000000',
+ avatar: Mock.Random.image('100x100', '#4096ff', '#fff', 'png', 'A'),
+ role: '管理员',
+ department: '运维部',
+ permissions: '*',
+ status: 1,
+ remark: '拥有所有权限',
+ lastLogin: Mock.Random.datetime(),
+ createdBy: 'system',
+ },
+ {
+ id: 2,
+ username: 'guest',
+ password: 'guest',
+ real_name: '访客',
+ nick_name: '访客',
+ mobile: '15100000000',
+ avatar: Mock.Random.image('100x100', '#ff7f50', '#fff', 'png', 'G'),
+ role: '访客',
+ department: '外部',
+ permissions: '/dashboard,/users,/users/list',
+ status: 1,
+ remark: '',
+ lastLogin: Mock.Random.datetime(),
+ createdBy: 'system',
+ },
+];
+
+// users list
+let users = Mock.mock({
+ 'data|20-30': [
+ {
+ id: '@id',
+ name: '@name',
+ nickName: '@last',
+ phone: /^1[34578]\d{9}$/,
+ 'age|18-60': 1,
+ address: '@county(true)',
+ isMale: '@boolean',
+ email: '@email',
+ createTime: '@datetime',
+ avatar() {
+ return Mock.Random.image(
+ '100x100',
+ '#4096ff',
+ '#757575',
+ 'png',
+ this.nickName ? this.nickName.substr(0, 1) : '',
+ );
+ },
+ },
+ ],
+}).data;
+
+// routes database (source of truth)
+const ROUTES_DB = [
+ { id: '1', name: '仪表盘', icon: 'home', route: '/dashboard' },
+ { id: '2', name: 'Ai会话', icon: 'robot', route: '/ai/chat' },
+ { id: '3', name: '用户管理', icon: 'user', route: '/users' },
+ {
+ id: '4',
+ breadcrumbParentId: '3',
+ name: '用户列表',
+ icon: 'team',
+ route: '/users/list',
+ },
+ {
+ id: '5',
+ menuParentId: '-1',
+ breadcrumbParentId: '4',
+ name: '用户详情',
+ route: '/users/:id',
+ },
+ { id: '6', name: '系统设置', icon: 'settings', route: '/sys' },
+ {
+ id: '7',
+ breadcrumbParentId: '6',
+ name: '成员列表',
+ icon: 'team',
+ route: '/admin/list',
+ },
+ {
+ id: '8',
+ menuParentId: '-1',
+ breadcrumbParentId: '6',
+ name: '成员详情',
+ icon: 'user',
+ route: '/admin/:id',
+ },
+ {
+ id: '9',
+ breadcrumbParentId: '6',
+ name: '菜单设置',
+ icon: 'tool',
+ route: '/sys/menu',
+ },
+ { id: '10', name: '项目介绍', icon: 'info-c', route: '/guide' },
+];
+
+// convert routes DB into menus tree used by UI
+function buildMenusFromRoutes(db) {
+ const items = db
+ // exclude routes that should not appear in menu (explicit menuParentId === '-1')
+ .filter((r) => !(r.menuParentId === '-1' || r.menuParentId === -1))
+ .map((r, idx) => {
+ const id = Number(r.id) || r.id;
+ // prefer explicit menuParentId, fall back to breadcrumbParentId
+ const parentIdRaw = r.breadcrumbParentId ?? null;
+ const parentId =
+ parentIdRaw === undefined || parentIdRaw === null
+ ? null
+ : parentIdRaw === '-1'
+ ? null
+ : Number(parentIdRaw);
+ return {
+ id,
+ title: r.name,
+ icon: r.icon,
+ route: r.route,
+ parentId,
+ status: 1,
+ sort: idx + 1,
+ children: [],
+ };
+ });
+
+ const map = new Map();
+ items.forEach((it) => map.set(it.id, it));
+ const roots = [];
+ items.forEach((it) => {
+ if (it.parentId == null || !map.has(it.parentId)) {
+ roots.push(it);
+ } else {
+ map.get(it.parentId).children.push(it);
+ }
+ });
+
+ // sort recursively by sort
+ const sortRec = (arr) => {
+ arr.sort((a, b) => (a.sort || 0) - (b.sort || 0));
+ arr.forEach((c) => {
+ if (c.children && c.children.length) sortRec(c.children);
+ });
+ };
+ sortRec(roots);
+ return roots;
+}
+
+const menus = buildMenusFromRoutes(ROUTES_DB);
+
+// dashboard
+const Dashboard = Mock.mock({
+ 'sales|6': [
+ {
+ 'name|+1': 2008,
+ 'Clothes|200-500': 1,
+ 'Food|180-400': 1,
+ 'Electronics|300-550': 1,
+ },
+ ],
+ cpu: { 'usage|50-600': 1, space: 825, 'cpu|40-90': 1 },
+ browser: [
+ { name: 'Google Chrome', percent: 43.3, status: 1 },
+ { name: 'Mozilla Firefox', percent: 33.4, status: 2 },
+ ],
+});
+
+// simple helper to read cookie token
+function parseTokenFromCookie(headers) {
+ const cookie = headers.cookie || '';
+ const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' });
+ if (!cookies.token) return null;
+ try {
+ return JSON.parse(cookies.token);
+ } catch (e) {
+ return null;
+ }
+}
+
+module.exports = {
+ [`GET ${ApiPrefix}/dashboard`](req, res) {
+ res.json({ success: true, data: Dashboard });
+ },
+
+ [`GET ${ApiPrefix}/menus`](req, res) {
+ res.json({ success: true, data: menus });
+ },
+
+ [`POST ${ApiPrefix}/admin/login`](req, res) {
+ const { username, password } = req.body || {};
+ const admin = adminList.find(
+ (u) => u.username === username && u.password === password,
+ );
+
+ if (admin) {
+ const now = new Date();
+ now.setDate(now.getDate() + 1);
+ res.cookie(
+ 'token',
+ JSON.stringify({ id: admin.id, deadline: now.getTime() }),
+ { maxAge: 24 * 3600 * 1000, httpOnly: true },
+ );
+ res.json({ success: true, message: 'Ok', data: null });
+ } else {
+ res
+ .status(400)
+ .json({ success: false, message: 'Invalid credentials', data: null });
+ }
+ },
+
+ [`GET ${ApiPrefix}/admin/logout`](req, res) {
+ res.clearCookie('token');
+ res.json({ success: true, data: null });
+ },
+
+ [`GET ${ApiPrefix}/admin`](req, res) {
+ const token = parseTokenFromCookie(req.headers);
+ const response = {};
+ let admin = {};
+ if (!token) {
+ res
+ .status(200)
+ .json({ success: false, message: 'Not Login', data: null });
+ return;
+ }
+ response.success = token.deadline > new Date().getTime();
+ if (response.success) {
+ const adminItem = adminList.find((u) => u.id === token.id);
+ if (adminItem) {
+ const { password, ...other } = adminItem;
+ admin = other;
+ }
+ }
+ res.json({ success: response.success, data: admin || null });
+ },
+
+ [`GET ${ApiPrefix}/users`](req, res) {
+ const { query } = req;
+ let { pageSize, page, ...other } = query || {};
+ pageSize = Number(pageSize) || 10;
+ page = Number(page) || 1;
+ let newData = users.slice();
+ for (const key in other) {
+ if (Object.prototype.hasOwnProperty.call(other, key)) {
+ newData = newData.filter(
+ (item) => String(item[key]).indexOf(String(other[key])) > -1,
+ );
+ }
+ }
+ res.status(200).json({
+ success: true,
+ data: {
+ data: newData.slice((page - 1) * pageSize, page * pageSize),
+ total: newData.length,
+ },
+ });
+ },
+
+ [`GET ${ApiPrefix}/user/:id`](req, res) {
+ const { id } = req.params || {};
+ let item = users.find((u) => String(u.id) === String(id));
+ // 如果找不到用户,则生成一个带有相同 id 的 mock 用户,避免 404
+ if (!item) {
+ item = Mock.mock({
+ id: String(id),
+ name: '@name',
+ nickName: '@last',
+ phone: /^1[34578]\d{9}$/,
+ 'age|18-60': 1,
+ address: '@county(true)',
+ isMale: '@boolean',
+ email: '@email',
+ createTime: '@datetime',
+ avatar() {
+ return Mock.Random.image(
+ '100x100',
+ '#4096ff',
+ '#757575',
+ 'png',
+ this.nickName ? this.nickName.substr(0, 1) : '',
+ );
+ },
+ });
+ }
+ // enrich with some additional mock fields: phone model and browsing history
+ const enriched = {
+ ...item,
+ // ensure lastLogin exists for user detail view (used by UI)
+ lastLogin: item.lastLogin || Mock.Random.datetime(),
+ phoneModel: Mock.Random.pick([
+ 'iPhone 13',
+ 'iPhone 14 Pro',
+ 'Samsung S22',
+ 'Huawei P50',
+ 'Xiaomi 12',
+ ]),
+ browsingHistory: Mock.mock({
+ 'data|3-6': [
+ {
+ url: '@url',
+ title: '@title',
+ time: '@datetime',
+ },
+ ],
+ }).data,
+ };
+ const { ...other } = enriched;
+ res.status(200).json({ success: true, data: other });
+ },
+
+ [`GET ${ApiPrefix}/routes`](req, res) {
+ res.status(200).json({ success: true, data: ROUTES_DB });
+ },
+
+ // admin accounts CRUD
+ [`GET ${ApiPrefix}/admins`](req, res) {
+ res.status(200).json({
+ success: true,
+ data: { data: adminList, total: adminList.length },
+ });
+ },
+
+ [`GET ${ApiPrefix}/admin/:id`](req, res) {
+ const { id } = req.params || {};
+ const item = adminList.find((u) => String(u.id) === String(id));
+ if (!item)
+ return res
+ .status(404)
+ .json({ success: false, message: 'Not found', data: null });
+ const { password, ...other } = item;
+ res.status(200).json({ success: true, data: other });
+ },
+
+ [`POST ${ApiPrefix}/admin`](req, res) {
+ const body = req.body || {};
+ const id = adminList.length
+ ? Math.max(...adminList.map((u) => u.id)) + 1
+ : 1;
+
+ const next = { id, ...body };
+ adminList.push(next);
+ const { password, ...other } = next;
+ res.status(200).json({ success: true, data: other });
+ },
+
+ [`PATCH ${ApiPrefix}/admin/:id`](req, res) {
+ const { id } = req.params || {};
+ const body = req.body || {};
+ const idx = adminList.findIndex((u) => String(u.id) === String(id));
+ if (idx === -1)
+ return res
+ .status(404)
+ .json({ success: false, message: 'Not found', data: null });
+
+ adminList[idx] = { ...adminList[idx], ...body };
+ const { password, ...other } = adminList[idx];
+ res.status(200).json({ success: true, data: other });
+ },
+};
diff --git a/mock/post.js b/mock/post.js
deleted file mode 100644
index e8e0d7c..0000000
--- a/mock/post.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Mock, Constant } from './_utils'
-
-const { ApiPrefix } = Constant
-
-let postId = 0
-const database = Mock.mock({
- 'data|100': [
- {
- id() {
- postId += 1
- return postId + 10000
- },
- 'status|1-2': 1,
- title: '@title',
- author: '@last',
- categories: '@word',
- tags: '@word',
- 'views|10-200': 1,
- 'comments|10-200': 1,
- visibility: () => {
- return Mock.mock(
- '@pick(["Public",' + '"Password protected", ' + '"Private"])'
- )
- },
- date: '@dateTime',
- image() {
- return Mock.Random.image(
- '100x100',
- Mock.Random.color(),
- '#757575',
- 'png',
- this.author.substr(0, 1)
- )
- },
- },
- ],
-}).data
-
-module.exports = {
- [`GET ${ApiPrefix}/posts`](req, res) {
- const { query } = req
- let { pageSize, page, ...other } = query
- pageSize = pageSize || 10
- page = page || 1
-
- let newData = database
- for (let key in other) {
- if ({}.hasOwnProperty.call(other, key)) {
- newData = newData.filter(item => {
- if ({}.hasOwnProperty.call(item, key)) {
- return (
- String(item[key])
- .trim()
- .indexOf(decodeURI(other[key]).trim()) > -1
- )
- }
- return true
- })
- }
- }
-
- res.status(200).json({
- data: newData.slice((page - 1) * pageSize, page * pageSize),
- total: newData.length,
- })
- },
-}
diff --git a/mock/route.js b/mock/route.js
deleted file mode 100644
index fb0456b..0000000
--- a/mock/route.js
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Constant } from './_utils'
-const { ApiPrefix } = Constant
-
-const database = [
- {
- id: '1',
- icon: 'dashboard',
- name: 'Dashboard',
- zh: {
- name: '仪表盘'
- },
- 'pt-br': {
- name: 'Dashboard'
- },
- route: '/dashboard',
- },
- {
- id: '2',
- breadcrumbParentId: '1',
- name: 'Users',
- zh: {
- name: '用户管理'
- },
- 'pt-br': {
- name: 'Usuário'
- },
- icon: 'user',
- route: '/user',
- },
- {
- id: '7',
- breadcrumbParentId: '1',
- name: 'Posts',
- zh: {
- name: '用户管理'
- },
- 'pt-br': {
- name: 'Posts'
- },
- icon: 'shopping-cart',
- route: '/post',
- },
- {
- id: '21',
- menuParentId: '-1',
- breadcrumbParentId: '2',
- name: 'User Detail',
- zh: {
- name: '用户详情'
- },
- 'pt-br': {
- name: 'Detalhes do usuário'
- },
- route: '/user/:id',
- },
- {
- id: '3',
- breadcrumbParentId: '1',
- name: 'Request',
- zh: {
- name: 'Request'
- },
- 'pt-br': {
- name: 'Requisição'
- },
- icon: 'api',
- route: '/request',
- },
- {
- id: '4',
- breadcrumbParentId: '1',
- name: 'UI Element',
- zh: {
- name: 'UI组件'
- },
- 'pt-br': {
- name: 'Elementos UI'
- },
- icon: 'camera-o',
- },
- {
- id: '45',
- breadcrumbParentId: '4',
- menuParentId: '4',
- name: 'Editor',
- zh: {
- name: 'Editor'
- },
- 'pt-br': {
- name: 'Editor'
- },
- icon: 'edit',
- route: '/editor',
- },
- {
- id: '5',
- breadcrumbParentId: '1',
- name: 'Charts',
- zh: {
- name: 'Charts'
- },
- 'pt-br': {
- name: 'Graficos'
- },
- icon: 'code-o',
- },
- {
- id: '51',
- breadcrumbParentId: '5',
- menuParentId: '5',
- name: 'ECharts',
- zh: {
- name: 'ECharts'
- },
- 'pt-br': {
- name: 'ECharts'
- },
- icon: 'line-chart',
- route: '/chart/ECharts',
- },
- {
- id: '52',
- breadcrumbParentId: '5',
- menuParentId: '5',
- name: 'HighCharts',
- zh: {
- name: 'HighCharts'
- },
- 'pt-br': {
- name: 'HighCharts'
- },
- icon: 'bar-chart',
- route: '/chart/highCharts',
- },
- {
- id: '53',
- breadcrumbParentId: '5',
- menuParentId: '5',
- name: 'Rechartst',
- zh: {
- name: 'Rechartst'
- },
- 'pt-br': {
- name: 'Rechartst'
- },
- icon: 'area-chart',
- route: '/chart/Recharts',
- },
-]
-
-module.exports = {
- [`GET ${ApiPrefix}/routes`](req, res) {
- res.status(200).json(database)
- },
-}
diff --git a/mock/user.js b/mock/user.js
deleted file mode 100644
index 4ffd4d3..0000000
--- a/mock/user.js
+++ /dev/null
@@ -1,250 +0,0 @@
-import { Mock, Constant, randomAvatar } from './_utils'
-import qs from 'qs'
-
-const { ApiPrefix } = Constant
-
-let usersListData = Mock.mock({
- 'data|80-100': [
- {
- id: '@id',
- name: '@name',
- nickName: '@last',
- phone: /^1[34578]\d{9}$/,
- 'age|11-99': 1,
- address: '@county(true)',
- isMale: '@boolean',
- email: '@email',
- createTime: '@datetime',
- avatar() {
- return randomAvatar()
- },
- },
- ],
-})
-
-let database = usersListData.data
-
-const EnumRoleType = {
- ADMIN: 'admin',
- DEFAULT: 'guest',
- DEVELOPER: 'developer',
-}
-
-const userPermission = {
- DEFAULT: {
- visit: ['1', '2', '21', '7', '5', '51', '52', '53'],
- role: EnumRoleType.DEFAULT,
- },
- ADMIN: {
- role: EnumRoleType.ADMIN,
- },
- DEVELOPER: {
- role: EnumRoleType.DEVELOPER,
- },
-}
-
-const adminUsers = [
- {
- id: 0,
- username: 'admin',
- password: 'admin',
- permissions: userPermission.ADMIN,
- avatar: randomAvatar(),
- },
- {
- id: 1,
- username: 'guest',
- password: 'guest',
- permissions: userPermission.DEFAULT,
- avatar: randomAvatar(),
- },
- {
- id: 2,
- username: '吴彦祖',
- password: '123456',
- permissions: userPermission.DEVELOPER,
- avatar: randomAvatar(),
- },
-]
-
-const queryArray = (array, key, keyAlias = 'key') => {
- if (!(array instanceof Array)) {
- return null
- }
- let data
-
- for (let item of array) {
- if (item[keyAlias] === key) {
- data = item
- break
- }
- }
-
- if (data) {
- return data
- }
- return null
-}
-
-const NOTFOUND = {
- message: 'Not Found',
- documentation_url: 'http://localhost:8000/request',
-}
-
-module.exports = {
- [`POST ${ApiPrefix}/user/login`](req, res) {
- const { username, password } = req.body
- const user = adminUsers.filter(item => item.username === username)
-
- if (user.length > 0 && user[0].password === password) {
- const now = new Date()
- now.setDate(now.getDate() + 1)
- res.cookie(
- 'token',
- JSON.stringify({ id: user[0].id, deadline: now.getTime() }),
- {
- maxAge: 900000,
- httpOnly: true,
- }
- )
- res.json({ success: true, message: 'Ok' })
- } else {
- res.status(400).end()
- }
- },
-
- [`GET ${ApiPrefix}/user/logout`](req, res) {
- res.clearCookie('token')
- res.status(200).end()
- },
-
- [`GET ${ApiPrefix}/user`](req, res) {
- const cookie = req.headers.cookie || ''
- const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' })
- const response = {}
- let user = {}
- if (!cookies.token) {
- res.status(200).send({ message: 'Not Login' })
- return
- }
- const token = JSON.parse(cookies.token)
- if (token) {
- response.success = token.deadline > new Date().getTime()
- }
- if (response.success) {
- const userItem = adminUsers.find(_ => _.id === token.id)
- if (userItem) {
- const { password, ...other } = userItem
- user = other
- }
- }
- response.user = user
- res.json(response)
- },
-
- [`GET ${ApiPrefix}/users`](req, res) {
- const { query } = req
- let { pageSize, page, ...other } = query
- pageSize = pageSize || 10
- page = page || 1
-
- let newData = database
- for (let key in other) {
- if ({}.hasOwnProperty.call(other, key)) {
- newData = newData.filter(item => {
- if ({}.hasOwnProperty.call(item, key)) {
- if (key === 'address') {
- return other[key].every(iitem => item[key].indexOf(iitem) > -1)
- } else if (key === 'createTime') {
- const start = new Date(other[key][0]).getTime()
- const end = new Date(other[key][1]).getTime()
- const now = new Date(item[key]).getTime()
-
- if (start && end) {
- return now >= start && now <= end
- }
- return true
- }
- return (
- String(item[key])
- .trim()
- .indexOf(decodeURI(other[key]).trim()) > -1
- )
- }
- return true
- })
- }
- }
-
- res.status(200).json({
- data: newData.slice((page - 1) * pageSize, page * pageSize),
- total: newData.length,
- })
- },
-
- [`POST ${ApiPrefix}/users/delete`](req, res) {
- const { ids=[] } = req.body
- database = database.filter(item => !ids.some(_ => _ === item.id))
- res.status(204).end()
- },
-
- [`POST ${ApiPrefix}/user`](req, res) {
- const newData = req.body
- newData.createTime = Mock.mock('@now')
- newData.avatar =
- newData.avatar ||
- Mock.Random.image(
- '100x100',
- Mock.Random.color(),
- '#757575',
- 'png',
- newData.nickName.substr(0, 1)
- )
- newData.id = Mock.mock('@id')
-
- database.unshift(newData)
-
- res.status(200).end()
- },
-
- [`GET ${ApiPrefix}/user/:id`](req, res) {
- const { id } = req.params
- const data = queryArray(database, id, 'id')
- if (data) {
- res.status(200).json(data)
- } else {
- res.status(200).json(NOTFOUND)
- }
- },
-
- [`DELETE ${ApiPrefix}/user/:id`](req, res) {
- const { id } = req.params
- const data = queryArray(database, id, 'id')
- if (data) {
- database = database.filter(item => item.id !== id)
- res.status(204).end()
- } else {
- res.status(200).json(NOTFOUND)
- }
- },
-
- [`PATCH ${ApiPrefix}/user/:id`](req, res) {
- const { id } = req.params
- const editItem = req.body
- let isExist = false
-
- database = database.map(item => {
- if (item.id === id) {
- isExist = true
- return Object.assign({}, item, editItem)
- }
- return item
- })
-
- if (isExist) {
- res.status(201).end()
- } else {
- res.status(200).json(NOTFOUND)
- }
- },
-}
diff --git a/package.json b/package.json
index e84e8cb..2cf54b2 100644
--- a/package.json
+++ b/package.json
@@ -1,134 +1,56 @@
{
"name": "antd-admin",
- "version": "5.5.0",
- "license": "MIT",
+ "version": "6.0.0",
"description": "An admin dashboard application demo built upon Ant Design and UmiJS",
+ "license": "MIT",
+ "scripts": {
+ "build": "umi build",
+ "dev": "umi dev",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "format": "prettier --write .",
+ "lint": "eslint --ext .ts,.tsx src",
+ "lint:fix": "eslint --ext .ts,.tsx src --fix",
+ "start": "npm run dev"
+ },
"dependencies": {
- "@ant-design/icons": "^4.8.3",
- "@lingui/react": "^5.0.0",
- "@rc-component/picker": "^1.9.0",
- "antd": "^6.0.0",
- "axios": "^1.0.0",
- "classnames": "^2.5.1",
- "d3-shape": "^2.1.0",
- "dayjs": "^1.11.10",
- "draft-js": "^0.11.7",
- "draftjs-to-html": "^0.9.1",
- "draftjs-to-markdown": "^0.6.0",
- "dva-model-extend": "^0.1.2",
- "echarts": "^5.5.0",
- "echarts-for-react": "^3.0.2",
- "echarts-gl": "^2.0.9",
- "echarts-liquidfill": "^3.1.0",
- "enquire-js": "^0.2.1",
- "highcharts": "^11.2.0",
- "highcharts-react-official": "^3.2.3",
- "json-format": "^1.0.1",
- "lodash": "^4.17.21",
- "make-plural": "^7.3.0",
- "md5": "^2.3.0",
- "nprogress": "^0.2.0",
- "path-to-regexp": "^6.2.2",
- "prop-types": "^15.8.1",
- "qs": "^6.12.0",
- "react": "^18.2.0",
- "react-adsense": "^0.1.0",
- "react-countup": "^4.4.0",
- "react-dom": "^18.2.0",
- "react-draft-wysiwyg": "^1.15.0",
- "react-helmet": "^6.1.0",
- "react-perfect-scrollbar": "^1.5.8",
- "recharts": "^2.12.4",
- "store": "^2.0.12"
+ "@ant-design/icons": "^6.1.0",
+ "@ant-design/x": "^2.3.0",
+ "@ant-design/x-sdk": "^2.3.0",
+ "@umijs/plugins": "^4.6.32",
+ "antd": "^6.3.2",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "umi": "^4.6.32",
+ "zustand": "^5.0.0"
},
"devDependencies": {
- "@babel/core": "^7.29.0",
- "@babel/preset-react": "^7.24.1",
- "@lingui/cli": "^5.0.0",
- "@lingui/core": "^5.0.0",
- "@lingui/format-json": "^5.9.1",
- "@umijs/preset-react": "^1.8.32",
- "@lingui/macro": "^5.0.0",
- "babel-eslint": "^10.1.0",
- "babel-plugin-dev-expression": "^0.2.3",
- "babel-plugin-import": "^1.13.8",
- "babel-plugin-macros": "^3.1.0",
- "babel-plugin-module-resolver": "^4.1.0",
- "cross-env": "^7.0.3",
- "eslint": "^7.32.0",
- "eslint-config-react-app": "^6.0.0",
- "eslint-plugin-flowtype": "^5.10.0",
+ "@testing-library/jest-dom": "^6.1.1",
+ "@testing-library/react": "^14.0.0",
+ "@types/jest": "^29.5.3",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@typescript-eslint/eslint-plugin": "^6.9.0",
+ "@typescript-eslint/parser": "^6.9.0",
+ "@umijs/test": "^4.6.32",
+ "eslint": "^8.47.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.29.1",
- "eslint-plugin-jsx-a11y": "^6.8.0",
- "eslint-plugin-react": "^7.34.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-prettier": "^5.1.0",
+ "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
- "husky": "^4.3.8",
- "less-vars-to-js": "^1.3.0",
- "puppeteer-core": "^24.37.2",
- "lint-staged": "^10.5.4",
+ "eslint-plugin-unused-imports": "^2.0.0",
+ "jest": "^29.6.1",
"mockjs": "^1.1.0",
- "module": "^1.2.5",
"prettier": "^2.8.8",
- "stylelint": "^13.13.1",
- "stylelint-config-prettier": "^8.0.2",
- "stylelint-config-standard": "^21.0.0",
- "typescript": "^4.9.5",
- "umi": "^3.5.41"
- },
- "overrides": {
- "@babel/core": "^7.29.0"
- },
- "engines": {
- "node": ">= 10.0.0"
- },
- "lint-staged": {
- "src/**/*.js": [
- "eslint --ext .js --fix",
- "npm run prettier",
- "git add"
- ],
- "**/*.less": [
- "stylelint --syntax less",
- "npm run prettier",
- "git add"
- ]
- },
- "lingui": {
- "fallbackLocales": {
- "default": "en"
- },
- "sourceLocale": "en",
- "locales": [
- "en",
- "zh",
- "pt-br"
- ],
- "catalogs": [
- {
- "path": "src/locales/{locale}/messages",
- "include": [
- "src/pages",
- "src/layouts",
- "src/components",
- "src/layouts"
- ]
- }
- ]
- },
- "scripts": {
- "analyze": "cross-env ANALYZE=1 umi build",
- "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider umi build",
- "check:model": "umi dva list model",
- "lint:js": "eslint --ext .js src",
- "lint:style": "stylelint \"src/**/*.less\" --syntax less",
- "start": "cross-env NODE_OPTIONS=--openssl-legacy-provider umi dev",
- "test": "cross-env BABELRC=none umi test",
- "prettier": "prettier --write 'src/**/*.{js,less}'",
- "test:e2e": "node ./src/e2e/run-puppeteer.js",
- "precommit": "lint-staged",
- "add-locale": "lingui add-locale",
- "extract": "lingui extract",
- "trans": "lingui extract --clean && node ./scripts/translate.js",
- "doc": "docsify serve docs"
+ "prettier-plugin-organize-imports": "^3.2.2",
+ "prettier-plugin-packagejson": "^2.4.3",
+ "ts-jest": "^29.1.0",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.9.3",
+ "jest-environment-jsdom": "^29.6.1",
+ "jsdom": "^21.1.1"
}
}
diff --git a/public/america.svg b/public/america.svg
deleted file mode 100644
index 706cc00..0000000
--- a/public/america.svg
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/china.svg b/public/china.svg
deleted file mode 100644
index 709a1fe..0000000
--- a/public/china.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/portugal.svg b/public/portugal.svg
deleted file mode 100644
index 1e8ffbb..0000000
--- a/public/portugal.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/scripts/translate.js b/scripts/translate.js
deleted file mode 100644
index b2d9444..0000000
--- a/scripts/translate.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * Youdao Translate, My private account is for testing purposes only.
- * Please go to the official account to apply for an account. Thank you for your cooperation.
- * APP ID:055c2d71f9a05214
- * Secret key:ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN
- */
-
-/* eslint-disable */
-const md5 = require('md5')
-const qs = require('qs')
-const fs = require('fs')
-const path = require('path')
-const axios = require('axios')
-const jsonFormat = require('json-format')
-const { i18n } = require('../src/utils/config')
-const { languages, defaultLanguage } = i18n
-
-const locales = {}
-
-languages.forEach(item => {
- locales[item.key] = require(`../src/locales/${item.key}/messages.json`)
-})
-
-const youdao = ({ q, from, to }) =>
- new Promise((resolve, reject) => {
- {
- const appid = '055c2d71f9a05214'
- const appse = 'ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN'
- const salt = Date.now()
-
- const sign = md5(appid + q + salt + appse)
- const query = qs.stringify({
- q,
- from,
- to,
- appKey: appid,
- salt,
- sign,
- })
-
- axios.get(`http://openapi.youdao.com/api?${query}`).then(({ data }) => {
- if (data.query && data.translation[0]) {
- resolve(data.translation[0])
- } else {
- resolve(q)
- }
- })
- }
- })
-
-const transform = async ({ from, to, locales, outputPath }) => {
- for (const key in locales[from]) {
- if (locales[to][key]) {
- console.log(`add to skip: ${key}`)
- } else {
- let res = key
- let way = 'youdao'
- if (key.indexOf('/') !== 0) {
- const reg = '{([^{}]*)}'
- const tasks = key
- .match(new RegExp(`${reg}|((?<=(${reg}|^)).*?(?=(${reg}|$)))`, 'g'))
- .map(item => {
- if (new RegExp(reg).test(item)) {
- return Promise.resolve(item)
- }
- return youdao({
- q: item,
- from,
- to,
- })
- })
-
- res = (await Promise.all(tasks)).join('')
- } else {
- res = `/${to + key}`
- way = 'link'
- }
- if (res !== key) {
- locales[to][key] = res
- console.log(`${way}: ${from} -> ${to}: ${key} -> ${res}`)
- } else {
- console.log(`same: ${from} -> ${to}: ${key}`)
- }
- }
- }
- await fs.writeFileSync(
- path.resolve(__dirname, outputPath),
- jsonFormat(locales[to], {
- type: 'space',
- size: 2,
- })
- )
-}
-;(async () => {
- const tasks = languages
- .map(item => ({
- from: defaultLanguage,
- to: item.key,
- }))
- .filter(item => item.from !== item.to)
-
- for (const item of tasks) {
- console.log(`start: ${item.from} -> ${item.to}`)
- await transform({
- from: item.from,
- to: item.to,
- locales,
- outputPath: `../src/locales/${item.to}/messages.json`,
- })
- console.log(`completed: ${item.from} -> ${item.to}`)
- }
-
- console.log('All translations have been completed.')
-})()
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..80347e4
--- /dev/null
+++ b/src/app.tsx
@@ -0,0 +1,82 @@
+import { ConfigProvider } from 'antd';
+import 'antd/dist/reset.css';
+import enUS from 'antd/locale/en_US';
+import zhCN from 'antd/locale/zh_CN';
+import React, { useEffect, useState } from 'react';
+
+import AuthGate from './components/AuthGate';
+
+function LocaleWrapper({ children }: { children: React.ReactElement }) {
+ // read initial locale synchronously so Antd has correct locale on first render
+ const pick = (l: string | null) => {
+ if (l === 'zh-CN') return zhCN;
+ return enUS;
+ };
+ let initial: typeof zhCN | typeof enUS | undefined = undefined;
+ const stored = window.localStorage.getItem('umi_locale') || null;
+ initial = pick(stored);
+
+ const [locale, setLocale] = useState(
+ initial,
+ );
+
+ useEffect(() => {
+ const read = () => {
+ let l: string | null = null;
+ try {
+ l = window.localStorage.getItem('umi_locale') || null;
+ } catch (e) {
+ l = null;
+ }
+ setLocale(pick(l));
+ };
+
+ const onStorage = (ev: StorageEvent) => {
+ if (ev.key === 'umi_locale') read();
+ };
+
+ const onLocaleChange = (ev: Event) => {
+ // CustomEvent used by TopbarControls with detail = locale string
+ const possible = ev as CustomEvent | Event;
+ if (possible && typeof (possible as CustomEvent).detail !== 'undefined') {
+ const d = (possible as CustomEvent).detail;
+ if (d) setLocale(pick(String(d)));
+ }
+ };
+
+ window.addEventListener('storage', onStorage);
+ window.addEventListener('localechange', onLocaleChange as EventListener);
+ return () => {
+ window.removeEventListener('storage', onStorage);
+ window.removeEventListener(
+ 'localechange',
+ onLocaleChange as EventListener,
+ );
+ };
+ }, []);
+
+ return {children} ;
+}
+
+export function rootContainer(container: React.ReactElement) {
+ return (
+
+ {container}
+
+ );
+}
+
+// ensure mobile viewport meta exists (adds meta tag if not present)
+try {
+ if (typeof window !== 'undefined') {
+ const hasVp = document.querySelector('meta[name="viewport"]');
+ if (!hasVp) {
+ const m = document.createElement('meta');
+ m.name = 'viewport';
+ m.content = 'width=device-width,initial-scale=1,maximum-scale=1';
+ document.head.appendChild(m);
+ }
+ }
+} catch (e) {
+ // ignore in non-DOM environments
+}
diff --git a/src/components/AuthGate.tsx b/src/components/AuthGate.tsx
new file mode 100644
index 0000000..571a79a
--- /dev/null
+++ b/src/components/AuthGate.tsx
@@ -0,0 +1,63 @@
+import { Spin } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { history } from 'umi';
+
+import useGlobalSync from '@/hooks/useGlobalSync';
+import useIsMounted from '@/hooks/useIsMounted';
+import adminService from '../services/admin';
+
+import useGlobalStore from '../store/useGlobalStore';
+
+export default function AuthGate({ children }: { children: React.ReactNode }) {
+ const isLoggedIn = useGlobalSync((s) => s.isLoggedIn);
+ const [loading, setLoading] = useState(true);
+ const isMounted = useIsMounted();
+
+ useEffect(() => {
+ (async () => {
+ try {
+ const admin = (await adminService.currentAdmin()) as
+ | import('@/types/admin').Admin
+ | undefined;
+ if (!isMounted()) return;
+ if (admin) {
+ useGlobalStore.getState().login();
+ useGlobalStore.getState().setCurrentUser(admin);
+ } else {
+ useGlobalStore.getState().logout();
+ }
+ } catch (e) {
+ useGlobalStore.getState().logout();
+ } finally {
+ if (isMounted()) setLoading(false);
+ }
+ })();
+ }, [isMounted]);
+
+ useEffect(() => {
+ const path = history.location.pathname;
+ if (!isLoggedIn && path !== '/login') {
+ history.replace('/login');
+ }
+ if (isLoggedIn && path === '/login') {
+ history.replace('/');
+ }
+ }, [isLoggedIn]);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return <>{children}>;
+}
diff --git a/src/components/DropOption.test.tsx b/src/components/DropOption.test.tsx
new file mode 100644
index 0000000..d092f24
--- /dev/null
+++ b/src/components/DropOption.test.tsx
@@ -0,0 +1,64 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import React from 'react';
+import DropOption from './DropOption';
+
+// Mock antd Dropdown to expose menu items for testing
+jest.mock('antd', () => {
+ return {
+ Dropdown: ({ menu, children }: any) =>
+ React.createElement(
+ 'div',
+ null,
+ React.createElement('div', { 'data-testid': 'trigger' }, children),
+ React.createElement(
+ 'div',
+ { 'data-testid': 'menu' },
+ menu?.items?.map((it: any) =>
+ React.createElement(
+ 'button',
+ {
+ key: it.key,
+ 'data-key': it.key,
+ onClick: () => menu.onClick?.({ key: it.key }),
+ },
+ typeof it.label === 'string'
+ ? it.label
+ : React.createElement('span', null, it.label),
+ ),
+ ),
+ ),
+ ),
+ };
+});
+
+jest.mock('@ant-design/icons', () => ({
+ BarsOutlined: () => React.createElement('span', null, 'Bars'),
+ DownOutlined: () => React.createElement('span', null, 'Down'),
+}));
+
+test('renders default trigger when no children provided', () => {
+ render( );
+ const trigger = screen.getByTestId('trigger');
+ // default trigger in component is an
+ expect(trigger.querySelector('a')).toBeTruthy();
+});
+
+test('wraps plain text children in a span', () => {
+ render(PlainChild );
+ const trigger = screen.getByTestId('trigger');
+ expect(screen.getByText('PlainChild')).toBeTruthy();
+ expect(trigger.firstElementChild?.tagName.toLowerCase()).toBe('span');
+});
+
+test('clicking menu item calls onMenuClick with key', () => {
+ const onMenuClick = jest.fn();
+ render(
+ ,
+ );
+ const item = screen.getByText('One');
+ fireEvent.click(item);
+ expect(onMenuClick).toHaveBeenCalledWith('one');
+});
diff --git a/src/components/DropOption.tsx b/src/components/DropOption.tsx
new file mode 100644
index 0000000..f0286a4
--- /dev/null
+++ b/src/components/DropOption.tsx
@@ -0,0 +1,48 @@
+import { BarsOutlined, DownOutlined } from '@ant-design/icons';
+import { Dropdown } from 'antd';
+import React from 'react';
+
+type Option = { key: string; label: React.ReactNode };
+
+interface DropOptionProps {
+ menuOptions: Option[];
+ onMenuClick?: (key: string) => void;
+ children?: React.ReactNode;
+}
+
+const DropOption: React.FC = ({
+ menuOptions,
+ onMenuClick,
+ children,
+}) => {
+ const items = menuOptions.map((o) => ({ key: o.key, label: o.label }));
+
+ const menu = {
+ items,
+ onClick: ({ key }: { key: string }) => onMenuClick?.(key),
+ };
+
+ // ensure Dropdown receives a single React element as child
+ let triggerNode: React.ReactElement;
+ if (React.Children.count(children) === 1 && React.isValidElement(children)) {
+ triggerNode = children as React.ReactElement;
+ } else if (children) {
+ // wrap non-element children (like plain text) in a span
+ triggerNode = {children} ;
+ } else {
+ triggerNode = (
+ e.preventDefault()}>
+
+
+
+ );
+ }
+
+ return (
+
+ {triggerNode}
+
+ );
+};
+
+export default DropOption;
diff --git a/src/components/DropOption/DropOption.js b/src/components/DropOption/DropOption.js
deleted file mode 100644
index c4df678..0000000
--- a/src/components/DropOption/DropOption.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { BarsOutlined, DownOutlined } from '@ant-design/icons'
-import { Dropdown, Button, Menu } from 'antd'
-
-const DropOption = ({
- onMenuClick,
- menuOptions = [],
- buttonStyle,
- dropdownProps,
-}) => {
- const items = menuOptions.map(item => ({ key: item.key, label: item.name }))
-
- return (
- } {...dropdownProps}>
-
-
-
-
-
- )
-}
-
-DropOption.propTypes = {
- onMenuClick: PropTypes.func,
- menuOptions: PropTypes.array.isRequired,
- buttonStyle: PropTypes.object,
- dropdownProps: PropTypes.object,
-}
-
-export default DropOption
diff --git a/src/components/DropOption/package.json b/src/components/DropOption/package.json
deleted file mode 100644
index 0a1dc40..0000000
--- a/src/components/DropOption/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "DropOption",
- "version": "0.0.0",
- "private": true,
- "main": "./DropOption.js"
-}
diff --git a/src/components/Editor/Editor.js b/src/components/Editor/Editor.js
deleted file mode 100644
index a89d167..0000000
--- a/src/components/Editor/Editor.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react'
-import { Editor } from 'react-draft-wysiwyg'
-import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
-import styles from './Editor.less'
-
-const DraftEditor = props => {
- return (
-
- )
-}
-
-export default DraftEditor
diff --git a/src/components/Editor/Editor.less b/src/components/Editor/Editor.less
deleted file mode 100644
index 2cea380..0000000
--- a/src/components/Editor/Editor.less
+++ /dev/null
@@ -1,106 +0,0 @@
-.wrapper {
- height: 500px;
-
- :global {
- .rdw-dropdownoption-default {
- padding: 6px;
- }
-
- .rdw-dropdown-optionwrapper {
- box-sizing: content-box;
- width: 100%;
- border-radius: 0 0 2px 2px;
- &:hover {
- box-shadow: none;
- }
- }
-
- .rdw-inline-wrapper {
- flex-wrap: wrap;
- margin-bottom: 0;
-
- .rdw-option-wrapper {
- margin-bottom: 6px;
- }
- }
-
- .rdw-option-active {
- box-shadow: 1px 1px 0 #e8e8e8 inset;
- }
-
- .rdw-colorpicker-option {
- box-shadow: none;
- }
-
- .rdw-colorpicker-modal,
- .rdw-embedded-modal,
- .rdw-emoji-modal,
- .rdw-image-modal,
- .rdw-link-modal {
- box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
- }
-
- .rdw-colorpicker-modal,
- .rdw-embedded-modal,
- .rdw-link-modal {
- height: auto;
- }
-
- .rdw-emoji-modal {
- width: 214px;
- }
-
- .rdw-colorpicker-modal {
- width: auto;
- }
-
- .rdw-embedded-modal-btn,
- .rdw-image-modal-btn,
- .rdw-link-modal-btn {
- height: 32px;
- margin-top: 12px;
- }
-
- .rdw-embedded-modal-input,
- .rdw-embedded-modal-size-input,
- .rdw-link-modal-input {
- padding: 2px 6px;
- height: 32px;
- }
-
- .rdw-dropdown-selectedtext {
- color: #000;
- }
-
- .rdw-dropdown-wrapper,
- .rdw-option-wrapper {
- min-width: 36px;
- transition: all 0.2s ease;
- height: 30px;
-
- &:active {
- box-shadow: 1px 1px 0 #e8e8e8 inset;
- }
-
- &:hover {
- box-shadow: 1px 1px 0 #e8e8e8;
- }
- }
-
- .rdw-dropdown-wrapper {
- min-width: 60px;
- }
-
- .rdw-editor-main {
- box-sizing: border-box;
- }
- }
-
- .editor {
- border: 1px solid #f1f1f1;
- padding: 5px;
- border-radius: 2px;
- height: auto;
- min-height: 200px;
- }
-}
diff --git a/src/components/Editor/package.json b/src/components/Editor/package.json
deleted file mode 100644
index 8069626..0000000
--- a/src/components/Editor/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Editor",
- "version": "0.0.0",
- "private": true,
- "main": "./Editor.js"
-}
diff --git a/src/components/Ellipsis/index.d.ts b/src/components/Ellipsis/index.d.ts
deleted file mode 100644
index 075fa78..0000000
--- a/src/components/Ellipsis/index.d.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import { TooltipProps } from 'antd/lib/tooltip';
-
-export interface EllipsisTooltipProps extends TooltipProps {
- title?: undefined;
- overlayStyle?: undefined;
-}
-
-export interface EllipsisProps {
- tooltip?: boolean | EllipsisTooltipProps;
- length?: number;
- lines?: number;
- style?: React.CSSProperties;
- className?: string;
- fullWidthRecognition?: boolean;
-}
-
-export function getStrFullLength(str: string): number;
-export function cutStrByFullLength(str: string, maxLength: number): string;
-
-export default class Ellipsis extends React.Component {}
diff --git a/src/components/Ellipsis/index.js b/src/components/Ellipsis/index.js
deleted file mode 100644
index f306d50..0000000
--- a/src/components/Ellipsis/index.js
+++ /dev/null
@@ -1,272 +0,0 @@
-import React, { Component } from 'react';
-import { Tooltip } from 'antd';
-import classNames from 'classnames';
-import styles from './index.less';
-
-/* eslint react/no-did-mount-set-state: 0 */
-/* eslint no-param-reassign: 0 */
-
-const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
-
-const TooltipOverlayStyle = {
- overflowWrap: 'break-word',
- wordWrap: 'break-word',
-};
-
-export const getStrFullLength = (str = '') =>
- str.split('').reduce((pre, cur) => {
- const charCode = cur.charCodeAt(0);
- if (charCode >= 0 && charCode <= 128) {
- return pre + 1;
- }
- return pre + 2;
- }, 0);
-
-export const cutStrByFullLength = (str = '', maxLength) => {
- let showLength = 0;
- return str.split('').reduce((pre, cur) => {
- const charCode = cur.charCodeAt(0);
- if (charCode >= 0 && charCode <= 128) {
- showLength += 1;
- } else {
- showLength += 2;
- }
- if (showLength <= maxLength) {
- return pre + cur;
- }
- return pre;
- }, '');
-};
-
-const getTooltip = ({ tooltip, overlayStyle, title, children }) => {
- if (tooltip) {
- const base = tooltip === true ? {} : { ...tooltip };
- const styleProp = overlayStyle ? { styles: { root: overlayStyle } } : {};
- const props = { ...base, ...styleProp, title };
- return {children} ;
- }
- return children;
-};
-
-const EllipsisText = ({ text, length, tooltip, fullWidthRecognition, ...other }) => {
- if (typeof text !== 'string') {
- throw new Error('Ellipsis children must be string.');
- }
- const textLength = fullWidthRecognition ? getStrFullLength(text) : text.length;
- if (textLength <= length || length < 0) {
- return {text} ;
- }
- const tail = '...';
- let displayText;
- if (length - tail.length <= 0) {
- displayText = '';
- } else {
- displayText = fullWidthRecognition ? cutStrByFullLength(text, length) : text.slice(0, length);
- }
-
- const spanAttrs = tooltip ? {} : { ...other };
- return getTooltip({
- tooltip,
- overlayStyle: TooltipOverlayStyle,
- title: text,
- children: (
-
- {displayText}
- {tail}
-
- ),
- });
-};
-
-export default class Ellipsis extends Component {
- state = {
- text: '',
- targetCount: 0,
- };
-
- componentDidMount() {
- if (this.node) {
- this.computeLine();
- }
- }
-
- componentDidUpdate(perProps) {
- const { lines } = this.props;
- if (lines !== perProps.lines) {
- this.computeLine();
- }
- }
-
- computeLine = () => {
- const { lines } = this.props;
- if (lines && !isSupportLineClamp) {
- const text = this.shadowChildren.innerText || this.shadowChildren.textContent;
- const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10);
- const targetHeight = lines * lineHeight;
- this.content.style.height = `${targetHeight}px`;
- const totalHeight = this.shadowChildren.offsetHeight;
- const shadowNode = this.shadow.firstChild;
-
- if (totalHeight <= targetHeight) {
- this.setState({
- text,
- targetCount: text.length,
- });
- return;
- }
-
- // bisection
- const len = text.length;
- const mid = Math.ceil(len / 2);
-
- const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode);
-
- this.setState({
- text,
- targetCount: count,
- });
- }
- };
-
- bisection = (th, m, b, e, text, shadowNode) => {
- const suffix = '...';
- let mid = m;
- let end = e;
- let begin = b;
- shadowNode.innerHTML = text.substring(0, mid) + suffix;
- let sh = shadowNode.offsetHeight;
-
- if (sh <= th) {
- shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
- sh = shadowNode.offsetHeight;
- if (sh > th || mid === begin) {
- return mid;
- }
- begin = mid;
- if (end - begin === 1) {
- mid = 1 + begin;
- } else {
- mid = Math.floor((end - begin) / 2) + begin;
- }
- return this.bisection(th, mid, begin, end, text, shadowNode);
- }
- if (mid - 1 < 0) {
- return mid;
- }
- shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
- sh = shadowNode.offsetHeight;
- if (sh <= th) {
- return mid - 1;
- }
- end = mid;
- mid = Math.floor((end - begin) / 2) + begin;
- return this.bisection(th, mid, begin, end, text, shadowNode);
- };
-
- handleRoot = n => {
- this.root = n;
- };
-
- handleContent = n => {
- this.content = n;
- };
-
- handleNode = n => {
- this.node = n;
- };
-
- handleShadow = n => {
- this.shadow = n;
- };
-
- handleShadowChildren = n => {
- this.shadowChildren = n;
- };
-
- render() {
- const { text, targetCount } = this.state;
- const {
- children,
- lines,
- length,
- className,
- tooltip,
- fullWidthRecognition,
- ...restProps
- } = this.props;
-
- const cls = classNames(styles.ellipsis, className, {
- [styles.lines]: lines && !isSupportLineClamp,
- [styles.lineClamp]: lines && isSupportLineClamp,
- });
-
- if (!lines && !length) {
- return (
-
- {children}
-
- );
- }
-
- // length
- if (!lines) {
- return (
-
- );
- }
-
- const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
-
- // support document.body.style.webkitLineClamp
- if (isSupportLineClamp) {
- const style = `#${id}{-webkit-line-clamp:${lines};-webkit-box-orient: vertical;}`;
-
- const node = (
-
-
- {children}
-
- );
-
- return getTooltip({
- tooltip,
- overlayStyle: TooltipOverlayStyle,
- title: children,
- children: node,
- });
- }
-
- const childNode = (
-
- {targetCount > 0 && text.substring(0, targetCount)}
- {targetCount > 0 && targetCount < text.length && '...'}
-
- );
-
- return (
-
-
- {getTooltip({
- tooltip,
- overlayStyle: TooltipOverlayStyle,
- title: text,
- children: childNode,
- })}
-
- {children}
-
-
- {text}
-
-
-
- );
- }
-}
diff --git a/src/components/Ellipsis/index.less b/src/components/Ellipsis/index.less
deleted file mode 100644
index 3c0360c..0000000
--- a/src/components/Ellipsis/index.less
+++ /dev/null
@@ -1,24 +0,0 @@
-.ellipsis {
- display: inline-block;
- width: 100%;
- overflow: hidden;
- word-break: break-all;
-}
-
-.lines {
- position: relative;
- .shadow {
- position: absolute;
- z-index: -999;
- display: block;
- color: transparent;
- opacity: 0;
- }
-}
-
-.lineClamp {
- position: relative;
- display: -webkit-box;
- overflow: hidden;
- text-overflow: ellipsis;
-}
diff --git a/src/components/Ellipsis/index.md b/src/components/Ellipsis/index.md
deleted file mode 100644
index 1875b17..0000000
--- a/src/components/Ellipsis/index.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: Ellipsis
-subtitle: 文本自动省略号
-cols: 1
-order: 10
----
-
-文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
-
-## API
-
-| 参数 | 说明 | 类型 | 默认值 |
-| -------------------- | ------------------------------------------------ | ------- | ------ |
-| tooltip | 移动到文本展示完整内容的提示 | boolean | - |
-| length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - |
-| lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1` |
-| fullWidthRecognition | 是否将全角字符的长度视为 2 来计算字符串长度 | boolean | - |
diff --git a/src/components/Ellipsis/index.test.js b/src/components/Ellipsis/index.test.js
deleted file mode 100644
index 4d057b2..0000000
--- a/src/components/Ellipsis/index.test.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { getStrFullLength, cutStrByFullLength } from './index';
-
-describe('test calculateShowLength', () => {
- it('get full length', () => {
- expect(getStrFullLength('一二,a,')).toEqual(8);
- });
- it('cut str by full length', () => {
- expect(cutStrByFullLength('一二,a,', 7)).toEqual('一二,a');
- });
- it('cut str when length small', () => {
- expect(cutStrByFullLength('一22三', 5)).toEqual('一22');
- });
-});
diff --git a/src/components/FilterItem/FilterItem.js b/src/components/FilterItem/FilterItem.js
deleted file mode 100644
index e701c8b..0000000
--- a/src/components/FilterItem/FilterItem.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import styles from './FilterItem.less'
-
-const FilterItem = ({ label = '', children }) => {
- const labelArray = label.split('')
- return (
-
- {labelArray.length > 0 && (
-
- {labelArray.map((item, index) => (
-
- {item}
-
- ))}
-
- )}
-
{children}
-
- )
-}
-
-FilterItem.propTypes = {
- label: PropTypes.string,
- children: PropTypes.element.isRequired,
-}
-
-export default FilterItem
diff --git a/src/components/FilterItem/FilterItem.less b/src/components/FilterItem/FilterItem.less
deleted file mode 100644
index c2e98ec..0000000
--- a/src/components/FilterItem/FilterItem.less
+++ /dev/null
@@ -1,17 +0,0 @@
-.filterItem {
- display: flex;
- justify-content: space-between;
-
- .labelWrap {
- width: 64px;
- line-height: 28px;
- margin-right: 12px;
- justify-content: space-between;
- display: flex;
- overflow: hidden;
- }
-
- .item {
- flex: 1;
- }
-}
diff --git a/src/components/FilterItem/package.json b/src/components/FilterItem/package.json
deleted file mode 100644
index 04c7d8a..0000000
--- a/src/components/FilterItem/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "FilterItem",
- "version": "0.0.0",
- "private": true,
- "main": "./FilterItem.js"
-}
diff --git a/src/components/GlobalFooter.tsx b/src/components/GlobalFooter.tsx
new file mode 100644
index 0000000..e4279c2
--- /dev/null
+++ b/src/components/GlobalFooter.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+interface LinkItem {
+ title: string;
+ href: string;
+}
+
+interface GlobalFooterProps {
+ links?: LinkItem[];
+ copyright?: React.ReactNode;
+ className?: string;
+}
+
+const GlobalFooter: React.FC = ({
+ links = [],
+ copyright,
+}) => {
+ return (
+
+ );
+};
+
+export default GlobalFooter;
diff --git a/src/components/GlobalFooter/index.d.ts b/src/components/GlobalFooter/index.d.ts
deleted file mode 100644
index efde77d..0000000
--- a/src/components/GlobalFooter/index.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-export interface GlobalFooterProps {
- links?: Array<{
- key?: string;
- title: React.ReactNode;
- href: string;
- blankTarget?: boolean;
- }>;
- copyright?: React.ReactNode;
- style?: React.CSSProperties;
- className?: string;
-}
-
-export default class GlobalFooter extends React.Component {}
diff --git a/src/components/GlobalFooter/index.js b/src/components/GlobalFooter/index.js
deleted file mode 100644
index 2784e26..0000000
--- a/src/components/GlobalFooter/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import classNames from 'classnames';
-import styles from './index.less';
-
-const GlobalFooter = ({ className, links, copyright }) => {
- const clsString = classNames(styles.globalFooter, className);
- return (
-
- {links && (
-
- )}
- {copyright && {copyright}
}
-
- );
-};
-
-export default GlobalFooter;
diff --git a/src/components/GlobalFooter/index.less b/src/components/GlobalFooter/index.less
deleted file mode 100644
index ac43595..0000000
--- a/src/components/GlobalFooter/index.less
+++ /dev/null
@@ -1,29 +0,0 @@
-/* @import '~antd/lib/style/themes/default.less'; */
-
-.globalFooter {
- margin: 48px 0 24px 0;
- padding: 0 16px;
- text-align: center;
-
- .links {
- margin-bottom: 8px;
-
- a {
- color: @text-color-secondary;
- transition: all 0.3s;
-
- &:not(:last-child) {
- margin-right: 40px;
- }
-
- &:hover {
- color: @text-color;
- }
- }
- }
-
- .copyright {
- color: @text-color-secondary;
- font-size: @font-size-base;
- }
-}
diff --git a/src/components/GlobalFooter/index.md b/src/components/GlobalFooter/index.md
deleted file mode 100644
index 5d6058f..0000000
--- a/src/components/GlobalFooter/index.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: GlobalFooter
-subtitle: 全局页脚
-cols: 1
-order: 7
----
-
-页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。
-
-## API
-
-| 参数 | 说明 | 类型 | 默认值 |
-| --------- | -------- | ---------------------------------------------------------------- | ------ |
-| links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - |
-| copyright | 版权信息 | ReactNode | - |
diff --git a/src/components/IconMap.tsx b/src/components/IconMap.tsx
new file mode 100644
index 0000000..5c08961
--- /dev/null
+++ b/src/components/IconMap.tsx
@@ -0,0 +1,33 @@
+import {
+ AppstoreOutlined,
+ HomeOutlined,
+ InfoCircleOutlined,
+ ProfileOutlined,
+ RobotOutlined,
+ SettingOutlined,
+ TeamOutlined,
+ ToolOutlined,
+ UserAddOutlined,
+ UserOutlined,
+} from '@ant-design/icons';
+import React from 'react';
+
+export const ICON_MAP: Record = {
+ home: ,
+ 'info-c': ,
+ user: ,
+ team: ,
+ profile: ,
+ 'user-add': ,
+ settings: ,
+ tool: ,
+ robot: ,
+ 'app-store': ,
+};
+
+export function Icon({ name }: { name?: string }) {
+ if (!name) return null;
+ return ICON_MAP[name] || null;
+}
+
+export default Icon;
diff --git a/src/components/Layout/Bread.js b/src/components/Layout/Bread.js
deleted file mode 100644
index 5a38ed5..0000000
--- a/src/components/Layout/Bread.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { Breadcrumb } from 'antd'
-import { Link, withRouter } from 'umi'
-import { i18n } from '@lingui/core'
-import iconMap from 'utils/iconMap'
-import { pathToRegexp } from 'path-to-regexp'
-import { queryAncestors } from 'utils'
-import styles from './Bread.less'
-
-@withRouter
-class Bread extends PureComponent {
- generateBreadcrumbItems = paths => {
- return paths
- .map((item, key) => {
- if (!item) return null
-
- const content = (
-
- {item.icon && {iconMap[item.icon]} }
- {item.name}
-
- )
-
- return ({
- key: item.id || key,
- title: paths.length - 1 !== key ? {content} : content,
- })
- })
- .filter(Boolean)
- }
- render() {
- const { routeList, location } = this.props
-
- // Find a route that matches the pathname.
- const currentRoute = routeList.find(
- (_) => _.route && pathToRegexp(_.route).exec(location.pathname)
- )
-
- // Find the breadcrumb navigation of the current route match and all its ancestors.
- const paths = currentRoute
- ? queryAncestors(routeList, currentRoute, 'breadcrumbParentId').reverse()
- : [
- routeList[0],
- {
- id: 404,
- name: i18n._('Not Found'),
- },
- ]
-
- const items = this.generateBreadcrumbItems(paths)
-
- return
- }
-}
-
-Bread.propTypes = {
- routeList: PropTypes.array,
-}
-
-export default Bread
diff --git a/src/components/Layout/Bread.less b/src/components/Layout/Bread.less
deleted file mode 100644
index d30a5f1..0000000
--- a/src/components/Layout/Bread.less
+++ /dev/null
@@ -1,16 +0,0 @@
-.bread {
- margin-bottom: 24px;
-
- :global {
- .ant-breadcrumb {
- display: flex;
- align-items: center;
- }
- }
-}
-
-@media (max-width: 767px) {
- .bread {
- margin-bottom: 12px;
- }
-}
diff --git a/src/components/Layout/Header.js b/src/components/Layout/Header.js
deleted file mode 100644
index 08e3b87..0000000
--- a/src/components/Layout/Header.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { Menu, Layout, Avatar, Popover, Badge, List } from 'antd'
-import { Ellipsis } from 'components'
-import {
- BellOutlined,
- RightOutlined,
- MenuFoldOutlined,
- MenuUnfoldOutlined,
-} from '@ant-design/icons'
-import { i18n } from '@lingui/core'
-import { getLocale, setLocale } from 'utils'
-import dayjs from 'dayjs'
-import classnames from 'classnames'
-import config from 'config'
-import styles from './Header.less'
-
-
-
-class Header extends PureComponent {
- handleClickMenu = e => {
- e.key === 'SignOut' && this.props.onSignOut()
- }
- render() {
- const {
- fixed,
- avatar,
- username,
- collapsed,
- notifications,
- onCollapseChange,
- onAllNotificationsRead,
- } = this.props
-
- const userMenuItems = [
- {
- key: 'user',
- label: (
-
-
- {i18n._('Hi,')}
-
- {username}
-
-
- ),
- children: [
- {
- key: 'SignOut',
- label: i18n._('Sign out'),
- },
- ],
- },
- ]
-
- const rightContent = [ ]
-
- if (config.i18n) {
- const { languages } = config.i18n
- const language = getLocale()
- const currentLanguage = languages.find(
- item => item.key === language
- )
-
- const languageItems = [
- {
- key: 'language',
- label: ,
- children: languages.map(item => ({
- key: item.key,
- label: (
- <>
-
- {item.title}
- >
- ),
- })),
- },
- ]
-
- rightContent.unshift(
- {
- setLocale(data.key)
- }}
- mode="horizontal"
- items={languageItems}
- />
- )
- }
-
- rightContent.unshift(
- document.querySelector('#primaryLayout')}
- content={
-
-
(
-
-
- {item.title}
-
- }
- description={dayjs(item.date).fromNow()}
- />
-
-
- )}
- />
- {notifications.length ? (
-
- {i18n._('Clear notifications')}
-
- ) : null}
-
- }
- >
-
-
-
-
- )
-
- return (
-
- )
- }
-}
-
-Header.propTypes = {
- fixed: PropTypes.bool,
- user: PropTypes.object,
- menus: PropTypes.array,
- collapsed: PropTypes.bool,
- onSignOut: PropTypes.func,
- notifications: PropTypes.array,
- onCollapseChange: PropTypes.func,
- onAllNotificationsRead: PropTypes.func,
-}
-
-export default Header
diff --git a/src/components/Layout/Header.less b/src/components/Layout/Header.less
deleted file mode 100644
index 0635d57..0000000
--- a/src/components/Layout/Header.less
+++ /dev/null
@@ -1,156 +0,0 @@
-@import '~themes/vars.less';
-
-.header {
- padding: 0;
- box-shadow: @shadow-2;
- position: relative;
- display: flex;
- justify-content: space-between;
- height: 72px;
- z-index: 9;
- align-items: center;
- background-color: #fff;
-
- &.fixed {
- position: fixed;
- top: 0;
- right: 0;
- width: ~'calc(100% - 256px)';
- z-index: 29;
- transition: width 0.2s;
-
- &.collapsed {
- width: ~'calc(100% - 80px)';
- }
- }
-
- :global {
- .ant-menu-submenu-title {
- height: 72px;
- display: flex;
- align-items: center;
- }
-
- .ant-menu-horizontal {
- line-height: 72px;
-
- & > .ant-menu-submenu:hover {
- color: @primary-color;
- background-color: @hover-color;
- }
- }
-
- .ant-menu {
- border-bottom: none;
- height: 72px;
- }
-
- .ant-menu-horizontal > .ant-menu-submenu {
- top: 0;
- margin-top: 0;
- }
-
- .ant-menu-horizontal > .ant-menu-item,
- .ant-menu-horizontal > .ant-menu-submenu {
- border-bottom: none;
- }
-
- .ant-menu-horizontal > .ant-menu-item-active,
- .ant-menu-horizontal > .ant-menu-item-open,
- .ant-menu-horizontal > .ant-menu-item-selected,
- .ant-menu-horizontal > .ant-menu-item:hover,
- .ant-menu-horizontal > .ant-menu-submenu-active,
- .ant-menu-horizontal > .ant-menu-submenu-open,
- .ant-menu-horizontal > .ant-menu-submenu-selected,
- .ant-menu-horizontal > .ant-menu-submenu:hover {
- border-bottom: none;
- }
- }
-
- .rightContainer {
- display: flex;
- align-items: center;
- }
-
- .button {
- width: 72px;
- height: 72px;
- line-height: 72px;
- text-align: center;
- font-size: 18px;
- cursor: pointer;
- transition: @transition-ease-in;
-
- &:hover {
- color: @primary-color;
- background-color: @hover-color;
- }
- }
-}
-
-.iconButton {
- width: 48px;
- height: 48px;
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 24px;
- cursor: pointer;
- .background-hover();
-
- &:hover {
- .iconFont {
- color: @primary-color;
- }
- }
-
- & + .iconButton {
- margin-left: 8px;
- }
-
- .iconFont {
- color: #b2b0c7;
- font-size: 24px;
- }
-}
-
-.notification {
- padding: 24px 0;
- width: 320px;
- .notificationItem {
- transition: all 0.3s;
- padding: 12px 24px;
- cursor: pointer;
- &:hover {
- background-color: @hover-color;
- }
- }
- .clearButton {
- text-align: center;
- height: 48px;
- line-height: 48px;
- cursor: pointer;
- .background-hover();
- }
-}
-
-.notificationPopover {
- :global {
- .ant-popover-inner-content {
- padding: 0;
- }
- .ant-popover-arrow {
- display: none;
- }
- .ant-list-item-content {
- flex: 0;
- margin-left: 16px;
- }
- }
-}
-
-@media (max-width: 767px) {
- .header {
- width: 100% !important;
- }
-}
diff --git a/src/components/Layout/Menu.js b/src/components/Layout/Menu.js
deleted file mode 100644
index dbf18f8..0000000
--- a/src/components/Layout/Menu.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { Menu } from 'antd'
-import { NavLink, withRouter } from 'umi'
-import { pathToRegexp } from 'path-to-regexp'
-import { arrayToTree, queryAncestors } from 'utils'
-import iconMap from 'utils/iconMap'
-import store from 'store'
-
-// Use `items` prop for Menu (avoid JSX children like Menu.Item/SubMenu)
-
-@withRouter
-class SiderMenu extends PureComponent {
- state = {
- openKeys: store.get('openKeys') || [],
- }
-
- onOpenChange = openKeys => {
- const { menus } = this.props
- const rootSubmenuKeys = menus.filter(_ => !_.menuParentId).map(_ => _.id)
-
- const latestOpenKey = openKeys.find(
- key => this.state.openKeys.indexOf(key) === -1
- )
-
- let newOpenKeys = openKeys
- if (rootSubmenuKeys.indexOf(latestOpenKey) !== -1) {
- newOpenKeys = latestOpenKey ? [latestOpenKey] : []
- }
-
- this.setState({
- openKeys: newOpenKeys,
- })
- store.set('openKeys', newOpenKeys)
- }
-
- generateMenuItems = data => {
- return data.map(item => {
- if (item.children) {
- return {
- key: item.id,
- label: (
-
- {item.icon && iconMap[item.icon]}
- {item.name}
-
- ),
- children: this.generateMenuItems(item.children),
- }
- }
-
- return {
- key: item.id,
- label: (
-
- {item.icon && iconMap[item.icon]}
- {item.name}
-
- ),
- }
- })
- }
-
- render() {
- const {
- collapsed,
- theme,
- menus,
- location,
- isMobile,
- onCollapseChange,
- } = this.props
-
- // Generating tree-structured data for menu content.
- const menuTree = arrayToTree(menus, 'id', 'menuParentId')
-
- // Find a menu that matches the pathname.
- const currentMenu = menus.find(
- _ => _.route && pathToRegexp(_.route).exec(location.pathname)
- )
-
- // Find the key that should be selected according to the current menu.
- const selectedKeys = currentMenu
- ? queryAncestors(menus, currentMenu, 'menuParentId').map(_ => _.id)
- : []
-
- const menuProps = collapsed
- ? {}
- : {
- openKeys: this.state.openKeys,
- }
-
- const items = this.generateMenuItems(menuTree)
-
- return (
- {
- onCollapseChange(true)
- }
- : undefined
- }
- items={items}
- {...menuProps}
- />
- )
- }
-}
-
-SiderMenu.propTypes = {
- menus: PropTypes.array,
- theme: PropTypes.string,
- isMobile: PropTypes.bool,
- onCollapseChange: PropTypes.func,
-}
-
-export default SiderMenu
diff --git a/src/components/Layout/Sider.js b/src/components/Layout/Sider.js
deleted file mode 100644
index fcd51e8..0000000
--- a/src/components/Layout/Sider.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { Switch, Layout } from 'antd'
-import { i18n } from '@lingui/core'
-import { BulbOutlined } from '@ant-design/icons'
-import ScrollBar from '../ScrollBar'
-import { config } from 'utils'
-import SiderMenu from './Menu'
-import styles from './Sider.less'
-
-class Sider extends PureComponent {
- render() {
- const {
- menus,
- theme,
- isMobile,
- collapsed,
- onThemeChange,
- onCollapseChange,
- } = this.props
-
- return (
- {}}
- className={styles.sider}
- >
-
-
-
- {!collapsed &&
{config.siteName} }
-
-
-
-
-
-
-
-
- {!collapsed && (
-
-
-
- {i18n._('Switch Theme')}
-
-
-
- )}
-
- )
- }
-}
-
-Sider.propTypes = {
- menus: PropTypes.array,
- theme: PropTypes.string,
- isMobile: PropTypes.bool,
- collapsed: PropTypes.bool,
- onThemeChange: PropTypes.func,
- onCollapseChange: PropTypes.func,
-}
-
-export default Sider
diff --git a/src/components/Layout/Sider.less b/src/components/Layout/Sider.less
deleted file mode 100644
index 9f41821..0000000
--- a/src/components/Layout/Sider.less
+++ /dev/null
@@ -1,110 +0,0 @@
-@import '~themes/vars.less';
-
-.sider {
- box-shadow: fade(@primary-color, 10%) 0 0 28px 0;
- z-index: 10;
- :global {
- .ant-layout-sider-children {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- }
- }
-}
-
-.brand {
- z-index: 1;
- height: 72px;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0 24px;
- box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2);
- .logo {
- display: flex;
- align-items: center;
- justify-content: center;
-
- img {
- width: 36px;
- margin-right: 8px;
- }
-
- h1 {
- vertical-align: text-bottom;
- font-size: 16px;
- text-transform: uppercase;
- display: inline-block;
- font-weight: 700;
- color: @primary-color;
- white-space: nowrap;
- margin-bottom: 0;
- .text-gradient();
-
- :local {
- animation: fadeRightIn 300ms @ease-in-out;
- animation-fill-mode: both;
- }
- }
- }
-}
-
-.menuContainer {
- height: ~'calc(100vh - 120px)';
- overflow-x: hidden;
- flex: 1;
- padding: 24px 0;
-
- &::-webkit-scrollbar-thumb {
- background-color: transparent;
- }
-
- &:hover {
- &::-webkit-scrollbar-thumb {
- background-color: rgba(0, 0, 0, 0.2);
- }
- }
-
- :global {
- .ant-menu-inline {
- border-right: none;
- }
- }
-}
-
-.switchTheme {
- width: 100%;
- height: 48px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 0 16px;
- overflow: hidden;
- transition: all 0.3s;
-
- span {
- white-space: nowrap;
- overflow: hidden;
- font-size: 12px;
- }
-
- :global {
- .anticon {
- min-width: 14px;
- margin-right: 4px;
- font-size: 14px;
- }
- }
-}
-
-@keyframes fadeLeftIn {
- 0% {
- transform: translateX(5px);
- opacity: 0;
- }
-
- 100% {
- transform: translateX(0);
- opacity: 1;
- }
-}
diff --git a/src/components/Layout/index.js b/src/components/Layout/index.js
deleted file mode 100644
index 3ad0894..0000000
--- a/src/components/Layout/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import Header from './Header'
-import Menu from './Menu'
-import Bread from './Bread'
-import Sider from './Sider'
-
-export { Header, Menu, Bread, Sider }
diff --git a/src/components/Loader/Loader.js b/src/components/Loader/Loader.js
deleted file mode 100644
index 5a5a021..0000000
--- a/src/components/Loader/Loader.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import classNames from 'classnames'
-import styles from './Loader.less'
-
-const Loader = ({ spinning = false, fullScreen }) => {
- return (
-
- )
-}
-
-Loader.propTypes = {
- spinning: PropTypes.bool,
- fullScreen: PropTypes.bool,
-}
-
-export default Loader
diff --git a/src/components/Loader/Loader.less b/src/components/Loader/Loader.less
deleted file mode 100644
index 9fa02dc..0000000
--- a/src/components/Loader/Loader.less
+++ /dev/null
@@ -1,67 +0,0 @@
-.loader {
- background-color: #fff;
- width: 100%;
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- z-index: 100000;
- display: flex;
- justify-content: center;
- align-items: center;
- opacity: 1;
- text-align: center;
-
- &.fullScreen {
- position: fixed;
- }
-
- .warpper {
- width: 100px;
- height: 100px;
- display: inline-flex;
- flex-direction: column;
- justify-content: space-around;
- }
-
- .inner {
- width: 40px;
- height: 40px;
- margin: 0 auto;
- text-indent: -12345px;
- border-top: 1px solid rgba(0, 0, 0, 0.08);
- border-right: 1px solid rgba(0, 0, 0, 0.08);
- border-bottom: 1px solid rgba(0, 0, 0, 0.08);
- border-left: 1px solid rgba(0, 0, 0, 0.7);
- border-radius: 50%;
- z-index: 100001;
-
- :local {
- animation: spinner 600ms infinite linear;
- }
- }
-
- .text {
- width: 100px;
- height: 20px;
- text-align: center;
- font-size: 12px;
- letter-spacing: 4px;
- color: #000;
- }
-
- &.hidden {
- z-index: -1;
- opacity: 0;
- transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s;
- }
-}
-@keyframes spinner {
- 0% {
- transform: rotate(0deg);
- }
-
- 100% {
- transform: rotate(360deg);
- }
-}
diff --git a/src/components/Loader/package.json b/src/components/Loader/package.json
deleted file mode 100644
index 15bde9a..0000000
--- a/src/components/Loader/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Loader",
- "version": "0.0.0",
- "private": true,
- "main": "./Loader.js"
-}
diff --git a/src/components/Page/Page.js b/src/components/Page/Page.js
deleted file mode 100644
index 532bf31..0000000
--- a/src/components/Page/Page.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import Loader from '../Loader'
-import styles from './Page.less'
-
-export default class Page extends Component {
- render() {
- const { className, children, loading = false, inner = false } = this.props
- const loadingStyle = {
- height: 'calc(100vh - 184px)',
- overflow: 'hidden',
- }
- return (
-
- {loading ? : ''}
- {children}
-
- )
- }
-}
-
-Page.propTypes = {
- className: PropTypes.string,
- children: PropTypes.node,
- loading: PropTypes.bool,
- inner: PropTypes.bool,
-}
diff --git a/src/components/Page/Page.less b/src/components/Page/Page.less
deleted file mode 100644
index e180131..0000000
--- a/src/components/Page/Page.less
+++ /dev/null
@@ -1,16 +0,0 @@
-@import '~themes/vars.less';
-
-.contentInner {
- background: #fff;
- padding: 24px;
- box-shadow: @shadow-1;
- min-height: ~'calc(100vh - 230px)';
- position: relative;
-}
-
-@media (max-width: 767px) {
- .contentInner {
- padding: 12px;
- min-height: ~'calc(100vh - 160px)';
- }
-}
diff --git a/src/components/Page/package.json b/src/components/Page/package.json
deleted file mode 100644
index a3811bd..0000000
--- a/src/components/Page/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Page",
- "version": "0.0.0",
- "private": true,
- "main": "./Page.js"
-}
diff --git a/src/components/ScrollBar/index.js b/src/components/ScrollBar/index.js
deleted file mode 100644
index ce752b5..0000000
--- a/src/components/ScrollBar/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import ScrollBar from 'react-perfect-scrollbar'
-import 'react-perfect-scrollbar/dist/css/styles.css'
-import './index.less'
-
-export default ScrollBar
diff --git a/src/components/ScrollBar/index.less b/src/components/ScrollBar/index.less
deleted file mode 100644
index 31e149b..0000000
--- a/src/components/ScrollBar/index.less
+++ /dev/null
@@ -1,31 +0,0 @@
-:global {
- .ps--active-x > .ps__rail-x,
- .ps--active-y > .ps__rail-y {
- background-color: transparent;
- }
-
- .ps__rail-x:hover > .ps__thumb-x,
- .ps__rail-x:focus > .ps__thumb-x {
- height: 8px;
- }
-
- .ps__rail-y:hover > .ps__thumb-y,
- .ps__rail-y:focus > .ps__thumb-y {
- width: 8px;
- }
-
- .ps__rail-y,
- .ps__rail-x {
- z-index: 9;
- }
-
- .ps__thumb-y {
- width: 4px;
- right: 4px;
- }
-
- .ps__thumb-x {
- height: 4px;
- bottom: 4px;
- }
-}
diff --git a/src/components/TopbarControls.tsx b/src/components/TopbarControls.tsx
new file mode 100644
index 0000000..f1d443e
--- /dev/null
+++ b/src/components/TopbarControls.tsx
@@ -0,0 +1,138 @@
+import useGlobalSync from '@/hooks/useGlobalSync';
+import {
+ BulbOutlined,
+ FullscreenExitOutlined,
+ FullscreenOutlined,
+ GlobalOutlined,
+ UserOutlined,
+} from '@ant-design/icons';
+import { Avatar, Button, Dropdown, Tooltip, type MenuProps } from 'antd';
+import { useEffect, useState } from 'react';
+import { setLocale as setUmiLocale } from 'umi';
+
+type Props = {
+ goProfile?: () => void;
+ onLogout?: () => void;
+};
+
+export default function TopbarControls({ goProfile, onLogout }: Props) {
+ const [isFullscreen, setIsFullscreen] = useState(false);
+ const [darkMode, setDarkMode] = useState(false);
+ const currentAdmin = useGlobalSync((s) => s.currentAdmin);
+ const isLoggedIn = useGlobalSync((s) => s.isLoggedIn);
+
+ useEffect(() => {
+ const dm = localStorage.getItem('app-dark-mode');
+ if (dm === 'true') {
+ setDarkMode(true);
+ document.documentElement.classList.add('dark-mode');
+ }
+ const onFs = () => setIsFullscreen(!!document.fullscreenElement);
+ document.addEventListener('fullscreenchange', onFs);
+ return () => document.removeEventListener('fullscreenchange', onFs);
+ }, []);
+
+ const toggleFullscreen = async () => {
+ try {
+ if (!document.fullscreenElement)
+ await document.documentElement.requestFullscreen();
+ else await document.exitFullscreen();
+ } catch (e) {
+ void 0;
+ }
+ };
+
+ const toggleDarkMode = () => {
+ const next = !darkMode;
+ setDarkMode(next);
+ if (next) document.documentElement.classList.add('dark-mode');
+ else document.documentElement.classList.remove('dark-mode');
+
+ localStorage.setItem('app-dark-mode', String(next));
+ };
+
+ const changeLocale = (l: string) => {
+ try {
+ // ensure umi locale stored, then notify app to update Antd locale immediately
+ setUmiLocale(l, true);
+ localStorage.setItem('umi_locale', l);
+ window.dispatchEvent(new CustomEvent('localechange', { detail: l }));
+ } catch (e) {
+ window.location.reload();
+ }
+ };
+
+ const localeMenuItems: MenuProps['items'] = [
+ { key: 'en-US', label: 'English' },
+ { key: 'zh-CN', label: '中文' },
+ ];
+
+ const profileMenuItems: MenuProps['items'] = [
+ { key: 'profile', label: '个人信息' },
+ { key: 'logout', label: '注销' },
+ ];
+
+ const handleProfileClick = (key: string) => {
+ if (key === 'profile') {
+ goProfile && goProfile();
+ } else if (key === 'logout') {
+ onLogout && onLogout();
+ }
+ };
+
+ return (
+
+
+ :
+ }
+ />
+
+
+
+ } />
+
+
+
changeLocale(String(key)),
+ }}
+ placement="bottomRight"
+ >
+ } />
+
+
+ {isLoggedIn && currentAdmin ? (
+
handleProfileClick(String(key)),
+ }}
+ placement="bottomRight"
+ >
+
+
: undefined}
+ />
+
+ {currentAdmin.real_name || currentAdmin.username}
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/components/index.js b/src/components/index.js
deleted file mode 100644
index d3d3750..0000000
--- a/src/components/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import Editor from './Editor'
-import FilterItem from './FilterItem'
-import DropOption from './DropOption'
-import Loader from './Loader'
-import ScrollBar from './ScrollBar'
-import GlobalFooter from './GlobalFooter'
-import Ellipsis from './Ellipsis'
-import * as MyLayout from './Layout/index.js'
-import Page from './Page'
-
-export { MyLayout, Editor, GlobalFooter, Ellipsis, FilterItem, DropOption, Loader, Page, ScrollBar }
diff --git a/src/e2e/run-puppeteer.js b/src/e2e/run-puppeteer.js
deleted file mode 100644
index e5178ce..0000000
--- a/src/e2e/run-puppeteer.js
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-const puppeteer = require('puppeteer-core');
-
-(async () => {
- // Use CHROME_PATH env var if provided, otherwise default macOS Chrome path
- const executablePath = process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
- const url = process.env.E2E_URL || 'http://localhost:7000';
-
- try {
- const browser = await puppeteer.launch({
- executablePath,
- headless: true,
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
- });
- const page = await browser.newPage();
- await page.goto(url, { waitUntil: 'networkidle2' });
- const title = await page.title();
- console.log('E2E: page title =>', title);
- await browser.close();
- if (!/antd-admin/i.test(title)) process.exit(1);
- process.exit(0);
- } catch (err) {
- console.error('E2E error:', err);
- process.exit(1);
- }
-})();
diff --git a/src/hooks/useGlobalSync.ts b/src/hooks/useGlobalSync.ts
new file mode 100644
index 0000000..6207e88
--- /dev/null
+++ b/src/hooks/useGlobalSync.ts
@@ -0,0 +1,17 @@
+import type { GlobalState } from '@/store/useGlobalStore';
+import useGlobalStore from '@/store/useGlobalStore';
+import { useSyncExternalStore } from 'react';
+
+type Selector = (s: GlobalState) => T;
+
+export default function useGlobalSync(selector: Selector) {
+ return useSyncExternalStore(
+ (listener: () => void) => {
+ // subscribe returns unsubscribe
+ const unsub = useGlobalStore.subscribe(listener);
+ return unsub;
+ },
+ () => selector(useGlobalStore.getState()),
+ () => selector(useGlobalStore.getState()),
+ );
+}
diff --git a/src/hooks/useIsMounted.ts b/src/hooks/useIsMounted.ts
new file mode 100644
index 0000000..ef3a284
--- /dev/null
+++ b/src/hooks/useIsMounted.ts
@@ -0,0 +1,13 @@
+import { useCallback, useEffect, useRef } from 'react';
+
+// Simple hook that returns a stable function to check mounted state
+export default function useIsMounted(): () => boolean {
+ const ref = useRef(true);
+ useEffect(() => {
+ ref.current = true;
+ return () => {
+ ref.current = false;
+ };
+ }, []);
+ return useCallback(() => ref.current, []);
+}
diff --git a/src/layouts/BaseLayout.js b/src/layouts/BaseLayout.js
deleted file mode 100644
index 4e36bed..0000000
--- a/src/layouts/BaseLayout.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'umi'
-import { Helmet } from 'react-helmet'
-import { Loader } from 'components'
-import { queryLayout } from 'utils'
-import NProgress from 'nprogress'
-import config from 'utils/config'
-import { withRouter } from 'umi'
-
-import PublicLayout from './PublicLayout'
-import PrimaryLayout from './PrimaryLayout'
-import './BaseLayout.less'
-
-const LayoutMap = {
- primary: PrimaryLayout,
- public: PublicLayout,
-}
-
-@withRouter
-@connect(({ loading }) => ({ loading }))
-class BaseLayout extends PureComponent {
- previousPath = ''
-
- render() {
- const { loading, children, location } = this.props
- const Container = LayoutMap[queryLayout(config.layouts, location.pathname)]
-
- const currentPath = location.pathname + location.search
- if (currentPath !== this.previousPath) {
- NProgress.start()
- }
-
- if (!loading.global) {
- NProgress.done()
- this.previousPath = currentPath
- }
-
- return (
-
-
- {config.siteName}
-
-
- {children}
-
- )
- }
-}
-
-BaseLayout.propTypes = {
- loading: PropTypes.object,
-}
-
-export default BaseLayout
diff --git a/src/layouts/BaseLayout.less b/src/layouts/BaseLayout.less
deleted file mode 100644
index d958d75..0000000
--- a/src/layouts/BaseLayout.less
+++ /dev/null
@@ -1,74 +0,0 @@
-@import '~themes/vars.less';
-@import '~themes/index.less';
-
-:global {
- #nprogress {
- pointer-events: none;
-
- .bar {
- background: @primary-color;
- position: fixed;
- z-index: 2048;
- top: 0;
- left: 0;
- right: 0;
- width: 100%;
- height: 2px;
- }
-
- .peg {
- display: block;
- position: absolute;
- right: 0;
- width: 100px;
- height: 100%;
- box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
- opacity: 1;
- transform: rotate(3deg) translate(0, -4px);
- }
-
- .spinner {
- display: block;
- position: fixed;
- z-index: 1031;
- top: 15px;
- right: 15px;
- }
-
- .spinner-icon {
- width: 18px;
- height: 18px;
- box-sizing: border-box;
- border: solid 2px transparent;
- border-top-color: @primary-color;
- border-left-color: @primary-color;
- border-radius: 50%;
-
- :local {
- animation: nprogress-spinner 400ms linear infinite;
- }
- }
- }
-
- .nprogress-custom-parent {
- overflow: hidden;
- position: relative;
-
- #nprogress {
- .bar,
- .spinner {
- position: absolute;
- }
- }
- }
-}
-
-@keyframes nprogress-spinner {
- 0% {
- transform: rotate(0deg);
- }
-
- 100% {
- transform: rotate(360deg);
- }
-}
diff --git a/src/layouts/BaseLayout.tsx b/src/layouts/BaseLayout.tsx
new file mode 100644
index 0000000..2ee86d6
--- /dev/null
+++ b/src/layouts/BaseLayout.tsx
@@ -0,0 +1,31 @@
+import GlobalFooter from '@/components/GlobalFooter';
+import config from '@/utils/config';
+import { Layout as AntdLayout } from 'antd';
+import React from 'react';
+
+const { Content } = AntdLayout;
+
+type Props = {
+ sider?: React.ReactNode;
+ header?: React.ReactNode;
+ children?: React.ReactNode;
+ className?: string;
+};
+
+export default function BaseLayout({
+ sider,
+ header,
+ children,
+ className,
+}: Props) {
+ return (
+
+ {sider}
+
+ {header}
+ {children}
+
+
+
+ );
+}
diff --git a/src/layouts/PrimaryLayout.js b/src/layouts/PrimaryLayout.js
deleted file mode 100644
index 7a2e5ac..0000000
--- a/src/layouts/PrimaryLayout.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { withRouter } from 'umi'
-import { connect } from 'umi'
-import { MyLayout, GlobalFooter } from 'components'
-import { Drawer, FloatButton, Layout } from 'antd';
-import { enquireScreen, unenquireScreen } from 'enquire-js'
-import { pathToRegexp } from 'path-to-regexp'
-import { config, getLocale } from 'utils'
-import Error from '../pages/404'
-import styles from './PrimaryLayout.less'
-import store from 'store'
-
-const { Content } = Layout
-const { Header, Bread, Sider } = MyLayout
-
-@withRouter
-@connect(({ app, loading }) => ({ app, loading }))
-class PrimaryLayout extends PureComponent {
- state = {
- isMobile: false,
- }
-
- componentDidMount() {
- this.enquireHandler = enquireScreen(mobile => {
- const { isMobile } = this.state
- if (isMobile !== mobile) {
- this.setState({
- isMobile: mobile,
- })
- }
- })
- }
-
- componentWillUnmount() {
- unenquireScreen(this.enquireHandler)
- }
-
- onCollapseChange = collapsed => {
- this.props.dispatch({
- type: 'app/handleCollapseChange',
- payload: collapsed,
- })
- }
-
- render() {
- const { app, location, dispatch, children } = this.props
- const { theme, collapsed, notifications } = app
- const user = store.get('user') || {}
- const permissions = store.get('permissions') || {}
- const routeList = store.get('routeList') || []
- const { isMobile } = this.state
- const { onCollapseChange } = this
-
- // Localized route name.
-
- const lang = getLocale()
- const newRouteList =
- lang !== 'en'
- ? routeList.map(item => {
- const { name, ...other } = item
- return {
- ...other,
- name: (item[lang] || {}).name || name,
- }
- })
- : routeList
-
- // Find a route that matches the pathname.
- const currentRoute = newRouteList.find(
- _ => _.route && pathToRegexp(_.route).exec(location.pathname)
- )
-
- // Query whether you have permission to enter this page
- const hasPermission = currentRoute
- ? permissions.visit.includes(currentRoute.id)
- : false
-
- // MenuParentId is equal to -1 is not a available menu.
- const menus = newRouteList.filter(_ => _.menuParentId !== '-1')
-
- const headerProps = {
- menus,
- collapsed,
- notifications,
- onCollapseChange,
- avatar: user.avatar,
- username: user.username,
- fixed: config.fixedHeader,
- onAllNotificationsRead() {
- dispatch({ type: 'app/allNotificationsRead' })
- },
- onSignOut() {
- dispatch({ type: 'app/signOut' })
- },
- }
-
- const siderProps = {
- theme,
- menus,
- isMobile,
- collapsed,
- onCollapseChange,
- onThemeChange(theme) {
- dispatch({
- type: 'app/handleThemeChange',
- payload: theme,
- })
- },
- }
-
- return (
- (
-
- {isMobile ? (
-
-
-
- ) : (
-
- )}
-
-
-
-
- {hasPermission ? children : }
-
- document.querySelector('#primaryLayout')}
- />
-
-
-
- )
- );
- }
-}
-
-PrimaryLayout.propTypes = {
- children: PropTypes.element.isRequired,
- location: PropTypes.object,
- dispatch: PropTypes.func,
- app: PropTypes.object,
- loading: PropTypes.object,
-}
-
-export default PrimaryLayout
diff --git a/src/layouts/PrimaryLayout.less b/src/layouts/PrimaryLayout.less
deleted file mode 100644
index 6ee60c9..0000000
--- a/src/layouts/PrimaryLayout.less
+++ /dev/null
@@ -1,58 +0,0 @@
-@import '~themes/vars.less';
-
-.backTop {
- right: 50px;
-
- :global {
- .ant-back-top-content {
- background: @primary-color;
- opacity: 0.3;
- transition: all 0.3s;
- box-shadow: 0 0 15px 1px rgba(69, 65, 78, 0.1);
-
- &:hover {
- opacity: 1;
- }
- }
- }
-}
-
-.content {
- padding: 24px;
- min-height: ~'calc(100% - 72px)';
- // overflow-y: scroll;
-}
-
-.container {
- height: 100vh;
- flex: 1;
- width: ~'calc(100% - 256px)';
- overflow-y: scroll;
- overflow-x: hidden;
-}
-
-.footer {
- background: #fff;
- margin-top: 0;
- margin-bottom: 0;
- padding-top: 24px;
- padding-bottom: 24px;
- min-height: 72px;
-}
-
-@media (max-width: 767px) {
- .content {
- padding: 12px;
- }
-
- .backTop {
- right: 20px;
- bottom: 20px;
- }
-
- .container {
- height: 100vh;
- flex: 1;
- width: 100%;
- }
-}
diff --git a/src/layouts/PrimaryLayout.tsx b/src/layouts/PrimaryLayout.tsx
new file mode 100644
index 0000000..c707266
--- /dev/null
+++ b/src/layouts/PrimaryLayout.tsx
@@ -0,0 +1,577 @@
+import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
+import { Breadcrumb, Button, Drawer, Layout, Menu, message } from 'antd';
+import React, { useEffect, useMemo, useState } from 'react';
+import { history, Link, useLocation } from 'umi';
+
+import { ICON_MAP } from '@/components/IconMap';
+import TopbarControls from '@/components/TopbarControls';
+import useGlobalSync from '@/hooks/useGlobalSync';
+import useIsMounted from '@/hooks/useIsMounted';
+
+import { Admin } from '@/types/admin';
+import config from '@/utils/config';
+import api from '@/utils/request';
+import useGlobalStore from '../store/useGlobalStore';
+
+import adminService from '../services/admin';
+import menuService from '../services/menu';
+import { MenuItem } from '../types/menu';
+import BaseLayout from './BaseLayout';
+import './index.less';
+
+const { Sider, Header } = Layout;
+
+// keep warned paths module-level so dev StrictMode remounts don't re-show warnings
+const WARNED_PATHS = new Set();
+
+type FrontendMenu = {
+ key: string;
+ title: string;
+ icon?: string;
+ path?: string;
+ children?: FrontendMenu[];
+};
+
+type RouteEntry = {
+ id: number | string;
+ route?: string;
+ name?: string;
+ breadcrumbParentId?: number | string | null;
+ icon?: string;
+};
+
+type BreadcrumbItem = {
+ key: string;
+ title: string;
+ path: string;
+ icon?: string;
+};
+
+function buildMenuItems(items: FrontendMenu[]): any[] {
+ return items.map((it: FrontendMenu) => {
+ const iconKey = it.icon || it.key;
+ if (it.children && it.children.length) {
+ return {
+ key: it.key,
+ icon: ICON_MAP[iconKey],
+ label: it.title,
+ children: buildMenuItems(it.children),
+ };
+ }
+ return {
+ key: it.key,
+ icon: ICON_MAP[iconKey],
+ label: {it.title},
+ };
+ });
+}
+
+export default function PrimaryLayout({
+ children,
+}: {
+ children?: React.ReactNode;
+}) {
+ const { pathname } = useLocation();
+ const currentAdmin = useGlobalSync((s) => s.currentAdmin) as
+ | Admin
+ | undefined;
+ const [collapsed, setCollapsed] = useState(false);
+ const [isMobile, setIsMobile] = useState(
+ typeof window !== 'undefined' ? window.innerWidth <= 768 : false,
+ );
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const [menus, setMenus] = useState(null);
+ const [routesDb, setRoutesDb] = useState(null);
+
+ useEffect(() => {
+ const v = localStorage.getItem('app-sider-collapsed');
+ if (v !== null) setCollapsed(v === 'true');
+ }, []);
+
+ useEffect(() => {
+ const onResize = () => setIsMobile(window.innerWidth <= 768);
+ window.addEventListener('resize', onResize);
+ return () => window.removeEventListener('resize', onResize);
+ }, []);
+
+ // load menus from API so admin changes (status/sort) reflect immediately
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ const res = await menuService.listMenus();
+ if (!isMounted()) return;
+ // 服务现在保证返回统一的 data(即数组或期望类型),不再兼容旧格式
+ setMenus(res as MenuItem[]);
+ })();
+ }, [isMounted]);
+
+ // load raw routes DB to support breadcrumb for parameterized routes not present in menus
+ useEffect(() => {
+ (async () => {
+ try {
+ const r = await api('/routes');
+ if (!isMounted()) return;
+ setRoutesDb(r as RouteEntry[]);
+ } catch (e) {
+ void e;
+ }
+ })();
+ }, [isMounted]);
+
+ const filteredMenu = useMemo(() => {
+ // normalize currentAdmin.permissions to an array of paths or '*' string
+ const permsRaw: Admin['permissions'] | null =
+ (currentAdmin && currentAdmin.permissions) || null;
+ let perms: string[] = [];
+ if (typeof permsRaw === 'string') {
+ perms = permsRaw
+ .split(',')
+ .map((s: string) => s.trim())
+ .filter(Boolean);
+ }
+
+ const isAllowed = (item: FrontendMenu): boolean => {
+ // if no permissions configured for this user, deny access to guarded items
+ if (!perms || !perms.length) return false;
+ if (item.path && perms.includes(item.path)) return true;
+ // also allow matching by menu key (e.g. perms may contain '/dashboard' while item.path is '/')
+ if (item.key && perms.includes('/' + String(item.key))) return true;
+ if (item.children && item.children.length) {
+ return item.children.some((c: FrontendMenu) => isAllowed(c));
+ }
+ return false;
+ };
+ // choose source menu: API-loaded menus (if available) transformed to front-end shape
+ const source = (() => {
+ if (!menus || !menus.length) return [];
+
+ // helper to normalize a tree node to expected front-end shape
+ const normalizeNode = (m: MenuItem): FrontendMenu => ({
+ key: m.route
+ ? String(m.route).replace(/^\//, '') || String(m.id)
+ : String(m.id),
+ title: m.title,
+ icon: m.icon || String(m.id),
+ path: m.route || '/',
+ children: Array.isArray(m.children)
+ ? m.children.map(normalizeNode)
+ : [],
+ });
+
+ // detect if API returned a nested tree (children present) or flat list (parentId fields)
+ const looksNested = menus.some(
+ (m: MenuItem) => Array.isArray(m.children) && m.children.length,
+ );
+ if (looksNested) {
+ const tree = menus
+ .filter((m: MenuItem) => m.status === undefined || m.status === 1)
+ .map(normalizeNode) as FrontendMenu[];
+ // sort recursively
+ const sortRec = (arr: FrontendMenu[]) => {
+ arr.sort((a: any, b: any) => (a.sort || 0) - (b.sort || 0));
+ arr.forEach(
+ (c) => c.children && sortRec(c.children as FrontendMenu[]),
+ );
+ };
+ sortRec(tree);
+ return tree;
+ }
+
+ // otherwise treat as flat list with parentId
+ const map = new Map();
+ const active = menus.filter(
+ (m: MenuItem) => m.status === undefined || m.status === 1,
+ );
+ active.forEach((m: MenuItem) =>
+ map.set(m.id, normalizeNode(m) as FrontendMenu),
+ );
+ const roots: FrontendMenu[] = [];
+ active.forEach((m: MenuItem) => {
+ const node = map.get(m.id) as FrontendMenu;
+ if (m.parentId === null || m.parentId === undefined) roots.push(node);
+ else {
+ const parent = map.get(m.parentId);
+ if (parent) {
+ parent.children = parent.children || [];
+ parent.children.push(node);
+ } else roots.push(node);
+ }
+ });
+ const sortRec = (arr: FrontendMenu[]) => {
+ arr.sort((a: any, b: any) => (a.sort || 0) - (b.sort || 0));
+ arr.forEach((c) => c.children && sortRec(c.children as FrontendMenu[]));
+ };
+ sortRec(roots);
+ return roots;
+ })();
+
+ // treat '*' as full access (return everything from source)
+ if (permsRaw === '*') return source;
+
+ return source.filter((it: FrontendMenu) => isAllowed(it));
+ }, [currentAdmin, menus]);
+
+ // route-level guard: prevent access to routes not covered by currentAdmin.permissions
+ useEffect(() => {
+ const path = pathname;
+ // allow public paths
+ if (!currentAdmin) return;
+
+ const permsRaw: Admin['permissions'] | null =
+ currentAdmin.permissions || null;
+ if (permsRaw === '*') return;
+
+ const perms =
+ typeof permsRaw === 'string'
+ ? permsRaw
+ .split(',')
+ .map((s) => s.trim())
+ .filter(Boolean)
+ : [];
+
+ const pathAllowedByPerm = (perm: string) => {
+ if (!perm) return false;
+ if (perm === path) return true;
+ // treat perm as pattern if it contains ':'
+ if (perm.indexOf(':') >= 0) {
+ try {
+ const pattern = '^' + perm.replace(/:[^/]+/g, '[^/]+') + '$';
+ const re = new RegExp(pattern);
+ return re.test(path);
+ } catch (e) {
+ return false;
+ }
+ }
+ return false;
+ };
+
+ const allowed = perms.some(pathAllowedByPerm);
+ if (!allowed) {
+ // also allow if filteredMenu contains a matching path/pattern
+ const menuAllows = (() => {
+ const checkItem = (it: FrontendMenu): boolean => {
+ if (it.path === path) return true;
+ if (typeof it.path === 'string' && it.path.indexOf(':') >= 0) {
+ try {
+ const pattern = '^' + it.path.replace(/:[^/]+/g, '[^/]+') + '$';
+ const re = new RegExp(pattern);
+ if (re.test(path)) return true;
+ } catch (e) {
+ void e;
+ }
+ }
+ if (it.children && it.children.length)
+ return it.children.some(checkItem);
+ return false;
+ };
+ return filteredMenu && filteredMenu.some(checkItem);
+ })();
+
+ if (!menuAllows) {
+ if (!WARNED_PATHS.has(path)) {
+ WARNED_PATHS.add(path);
+ message.warning('没有权限访问此页面');
+ }
+ if (history.location.pathname !== '/') history.replace('/');
+ }
+ }
+ }, [pathname, currentAdmin, filteredMenu, routesDb]);
+
+ const menuItems = useMemo(
+ () => buildMenuItems(filteredMenu as FrontendMenu[]),
+ [filteredMenu],
+ );
+
+ // try to find the active key from pathname
+ const selectedKeys = useMemo(() => {
+ const findKey = (items: FrontendMenu[]): string | null => {
+ for (const it of items) {
+ if (it.path === pathname) return it.key;
+ if (it.children) {
+ const child = findKey(it.children);
+ if (child) return child;
+ }
+ }
+ return null;
+ };
+ const k = findKey(filteredMenu);
+ return k ? [k] : [];
+ }, [pathname, filteredMenu]);
+
+ // find parent keys to open corresponding submenu
+ const defaultOpenKeys = useMemo(() => {
+ if (!selectedKeys.length) return [];
+ const target = selectedKeys[0];
+ const findParent = (items: FrontendMenu[]): string | null => {
+ for (const it of items) {
+ if (
+ it.children &&
+ it.children.some((c: FrontendMenu) => c.key === target)
+ )
+ return it.key;
+ if (it.children) {
+ const sub = findParent(it.children as FrontendMenu[]);
+ if (sub) return sub;
+ }
+ }
+ return null;
+ };
+ const p = findParent(filteredMenu);
+ return p ? [p] : [];
+ }, [selectedKeys, filteredMenu]);
+
+ const handleCollapse = (c: boolean) => {
+ setCollapsed(c);
+ localStorage.setItem('app-sider-collapsed', String(c));
+ };
+
+ const logout = useGlobalStore.getState().logout;
+
+ const sider = (
+
+ {config.siteName}
+
+
+ );
+
+ const header = (
+
+ );
+ // build breadcrumb trail from MENU based on pathname
+ const breadcrumb = useMemo(() => {
+ const find = (
+ items: FrontendMenu[],
+ path: string,
+ ): FrontendMenu[] | null => {
+ for (const it of items) {
+ if (it.path === path) return [it];
+ if (it.children) {
+ const sub = find(it.children, path);
+ if (sub) return [it, ...sub];
+ }
+ }
+ return null;
+ };
+
+ // exact match first
+ let trail = find(filteredMenu, pathname);
+ if (!trail) {
+ // attempt pattern matching for param routes like /admin/:id
+ const patternFind = (
+ items: FrontendMenu[],
+ path: string,
+ ): FrontendMenu[] | null => {
+ for (const it of items) {
+ if (typeof it.path === 'string' && it.path.indexOf(':') >= 0) {
+ try {
+ const pattern = '^' + it.path.replace(/:[^/]+/g, '[^/]+') + '$';
+ const re = new RegExp(pattern);
+ if (re.test(path)) return [it];
+ } catch (e) {
+ void e;
+ }
+ }
+ if (it.children) {
+ const sub = patternFind(it.children, path);
+ if (sub) return [it, ...sub];
+ }
+ }
+ return null;
+ };
+
+ // attempt prefix matching (e.g., /users -> /users/list)
+ const prefixFind = (
+ items: FrontendMenu[],
+ path: string,
+ ): FrontendMenu[] | null => {
+ for (const it of items) {
+ if (it.path && path.startsWith(it.path) && it.path !== '/')
+ return [it];
+ if (it.children) {
+ const sub = prefixFind(it.children, path);
+ if (sub) return [it, ...sub];
+ }
+ }
+ return null;
+ };
+
+ trail =
+ patternFind(filteredMenu, pathname) ||
+ prefixFind(filteredMenu, pathname);
+
+ // try matching against raw routes DB (includes entries excluded from menu via menuParentId === '-1')
+ if (!trail && routesDb && routesDb.length) {
+ const matchRoute = (routes: RouteEntry[]): RouteEntry | null => {
+ for (const it of routes) {
+ if (!it.route) continue;
+ // exact match
+ if (it.route === pathname) return it;
+ // param pattern match
+ if (typeof it.route === 'string' && it.route.indexOf(':') >= 0) {
+ try {
+ const pattern =
+ '^' + it.route.replace(/:[^/]+/g, '[^/]+') + '$';
+ const re = new RegExp(pattern);
+ if (re.test(pathname)) return it;
+ } catch (e) {
+ void e;
+ }
+ }
+ }
+ return null;
+ };
+
+ const found = matchRoute(routesDb);
+ if (found) {
+ const idToRoute = (id: number | string) =>
+ routesDb.find((x: RouteEntry) => String(x.id) === String(id));
+ const chain: BreadcrumbItem[] = [];
+ let cur: RouteEntry | null = found;
+ while (cur) {
+ // for the current (param) route, use the actual pathname so link works
+ chain.unshift({
+ key: String(cur.id),
+ title: String(cur.name || ''),
+ path: cur === found ? pathname : cur.route || '/',
+ icon: cur.icon,
+ });
+ if (!cur.breadcrumbParentId) break;
+ cur = idToRoute(cur.breadcrumbParentId) || null;
+ }
+ trail = chain.length ? chain : null;
+ }
+ }
+
+ trail = trail || [{ key: 'dashboard', title: 'Dashboard', path: '/' }];
+ }
+ return trail;
+ }, [pathname, filteredMenu, routesDb]);
+
+ // update document title from breadcrumb
+ useEffect(() => {
+ if (breadcrumb && breadcrumb.length) {
+ const last = breadcrumb[breadcrumb.length - 1];
+
+ document.title = `${last.title} - ${config.siteName}`;
+ } else {
+ document.title = config.siteName;
+ }
+ }, [breadcrumb]);
+
+ const pageBody = (
+
+
{
+ const IconNode =
+ ICON_MAP[(b as any).icon || (b as any).key] || null;
+ const titleNode = (
+
+ {IconNode}
+ {(b as any).title}
+
+ );
+ return {
+ key: (b as any).key,
+ title:
+ idx < breadcrumb.length - 1 ? (
+ {titleNode}
+ ) : (
+ titleNode
+ ),
+ };
+ },
+ )}
+ />
+ {children}
+
+ );
+
+ return (
+ <>
+
+ {pageBody}
+
+ setMobileOpen(false)}
+ styles={{ body: { padding: 0 } }}
+ className="mobile-menu-drawer"
+ >
+
+ {config.siteName}
+
+ setMobileOpen(false)}
+ />
+
+ >
+ );
+}
diff --git a/src/layouts/PublicLayout.js b/src/layouts/PublicLayout.js
deleted file mode 100644
index c1d319b..0000000
--- a/src/layouts/PublicLayout.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const PublicLayout = ({ children }) => children
-
-export default PublicLayout
diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx
new file mode 100644
index 0000000..0ac0051
--- /dev/null
+++ b/src/layouts/PublicLayout.tsx
@@ -0,0 +1,38 @@
+import { Layout } from 'antd';
+import React from 'react';
+
+import TopbarControls from '@/components/TopbarControls';
+
+const { Content } = Layout;
+
+export default function PublicLayout({
+ children,
+}: {
+ children?: React.ReactNode;
+}) {
+ return (
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/layouts/index.js b/src/layouts/index.js
deleted file mode 100644
index 8efd0ec..0000000
--- a/src/layouts/index.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, { Component } from 'react'
-import { withRouter } from 'umi'
-import { ConfigProvider } from 'antd'
-import { i18n } from "@lingui/core"
-import { I18nProvider } from '@lingui/react'
-import { getLocale } from 'utils'
-import config from 'utils/config'
-import { zh, en, pt } from 'make-plural/plurals'
-import enMessages from '../locales/en/messages.json'
-import zhMessages from '../locales/zh/messages.json'
-import ptMessages from '../locales/pt-br/messages.json'
-import zhCN from 'antd/locale/zh_CN'
-import enUS from 'antd/locale/en_US'
-import ptBR from 'antd/locale/pt_BR'
-
-import BaseLayout from './BaseLayout'
-
-i18n.loadLocaleData({
- en: { plurals: en },
- zh: { plurals: zh },
- 'pt-br': { plurals: pt }
-})
-
-// Activate the default language synchronously so I18nProvider doesn't render `null` in dev.
-// Avoid activating the user-selected language here because its messages may not be loaded yet.
-const configuredDefault = (config && config.i18n && config.i18n.defaultLanguage) || 'en'
-const _catalogs = {
- en: enMessages,
- zh: zhMessages,
- 'pt-br': ptMessages,
-}
-try {
- const initialCatalog = _catalogs[configuredDefault]
- if (initialCatalog) {
- i18n.load(configuredDefault, initialCatalog)
- }
- i18n.activate(configuredDefault)
-} catch (e) {
- // ignore activation errors during bootstrap
-}
-
-// antd
-const languages = {
- zh: zhCN,
- en: enUS,
- 'pt-br': ptBR
-}
-
-// remove unused defaultLanguage
-
-@withRouter
-class Layout extends Component {
- state = {
- }
-
- componentDidMount() {
- let language = getLocale()
- if (!languages[language]) language = configuredDefault
- this.loadCatalog(language).catch(() => {})
- }
-
- loadCatalog = async (lan) => {
- const language = lan || i18n.defaultLanguage
- try {
- const catalogModule = await import(`../locales/${language}/messages.json`)
- const catalog = catalogModule && catalogModule.default ? catalogModule.default : catalogModule
-
- i18n.load(language, catalog)
- i18n.activate(language)
- } catch (err) {
- // If loading fails, fall back to default language and log a warning.
- // eslint-disable-next-line no-console
- console.warn(`Failed to load messages for locale "${language}":`, err)
- }
- }
-
- render() {
- const { children } = this.props
-
- let language = getLocale()
-
- if (!languages[language]) language = configuredDefault
-
- return (
-
-
- {children}
-
-
- )
- }
-}
-
-export default Layout
diff --git a/src/layouts/index.less b/src/layouts/index.less
new file mode 100644
index 0000000..b1cfe39
--- /dev/null
+++ b/src/layouts/index.less
@@ -0,0 +1,235 @@
+.navs {
+ ul {
+ padding: 0;
+ list-style: none;
+ display: flex;
+ }
+ li {
+ margin-right: 1em;
+ }
+}
+
+.content {
+ padding: 24px;
+ min-height: calc(100vh - 64px);
+}
+
+.footer {
+ background: #fff;
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-top: 24px;
+ padding-bottom: 24px;
+ min-height: 72px;
+}
+
+.app-sider {
+ .logo {
+ height: 64px;
+ color: rgba(0, 0, 0, 0.85);
+ font-weight: 600;
+ font-size: 18px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 24px;
+ box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2);
+ }
+}
+
+/* Sider visual refinements (light) */
+.app-sider {
+ background: #ffffff;
+ color: rgba(0, 0, 0, 0.85);
+
+ .logo {
+ background: transparent;
+ letter-spacing: 0.2px;
+ }
+
+ /* Antd light menu tweaks */
+ .ant-menu {
+ padding-top: 6px;
+ }
+
+ .ant-menu-light {
+ background: transparent;
+ }
+
+ .ant-menu-light .ant-menu-item,
+ .ant-menu-light .ant-menu-submenu-title {
+ color: rgba(0, 0, 0, 0.75);
+ padding-left: 20px;
+ }
+
+ .ant-menu-light .ant-menu-item:hover {
+ background: rgba(0, 0, 0, 0.03);
+ color: rgba(0, 0, 0, 0.9);
+ }
+
+ .ant-menu-light .ant-menu-item-selected {
+ background: rgba(24, 144, 255, 0.08) !important;
+ color: rgba(0, 0, 0, 0.9) !important;
+ }
+
+ .ant-menu-light .ant-menu-item .anticon {
+ color: rgba(0, 0, 0, 0.55);
+ font-size: 16px;
+ }
+}
+
+.app-header {
+ background: #fff;
+ padding: 0 16px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
+}
+
+.app-header .ant-btn {
+ color: rgba(0, 0, 0, 0.65);
+}
+
+.app-header .ant-btn:hover {
+ color: rgba(0, 0, 0, 0.85);
+}
+
+.app-content {
+ min-height: 0;
+}
+
+.page-container {
+ padding: 12px 16px 16px;
+}
+
+.page-breadcrumb {
+ margin: 0 0 12px 6px;
+}
+
+.page-content {
+ min-height: calc(100vh - 180px);
+}
+
+/* Mobile responsive tweaks */
+@media (max-width: 768px) {
+ .app-sider {
+ display: none !important;
+ }
+
+ .app-header {
+ padding: 0 8px;
+ }
+
+ .logo {
+ font-size: 16px;
+ padding: 0 12px;
+ }
+
+ .page-container {
+ padding: 8px;
+ }
+
+ .page-breadcrumb {
+ display: none;
+ }
+
+ .page-content {
+ min-height: auto;
+ }
+
+ .ant-menu-item,
+ .ant-menu-submenu-title {
+ padding-left: 12px !important;
+ }
+
+ .mobile-menu-drawer .ant-drawer-body {
+ padding: 0;
+ }
+
+ /* allow tables to scroll horizontally on small screens */
+ .ant-table-wrapper {
+ overflow-x: auto;
+ }
+
+ .ant-table-thead > tr > th,
+ .ant-table-tbody > tr > td {
+ white-space: nowrap;
+ }
+}
+
+/* Dark mode overrides */
+.dark-mode {
+ --bg: #121212;
+ --muted: rgba(255, 255, 255, 0.65);
+ --panel: #1f1f1f;
+ color: rgba(255, 255, 255, 0.88);
+ background: var(--bg);
+}
+
+.dark-mode .app-header {
+ background: #141414;
+ box-shadow: none;
+}
+
+.dark-mode .app-sider {
+ background: #111111;
+}
+
+/* Stronger dark-mode sider contrast */
+.dark-mode .app-sider {
+ background: linear-gradient(180deg, #08090b 0%, #0b0f13 100%);
+}
+
+.dark-mode .app-sider .logo {
+ background: rgba(255, 255, 255, 0.02);
+}
+
+.dark-mode .logo {
+ color: #fff;
+}
+
+.dark-mode .app-content,
+.dark-mode .page-content {
+ background: var(--panel);
+}
+
+.dark-mode .page-content {
+ background: transparent;
+}
+
+.page-content .ant-card {
+ border-radius: 10px;
+}
+
+.dark-mode .ant-breadcrumb,
+.dark-mode .page-breadcrumb {
+ color: var(--muted);
+}
+
+.dark-mode .ant-menu-dark .ant-menu-item,
+.dark-mode .ant-menu-dark .ant-menu-submenu-title {
+ color: var(--muted);
+}
+
+/* smooth transition for mode change */
+html,
+body,
+.ant-layout,
+.ant-layout .ant-layout {
+ transition: background 0.25s ease, color 0.25s ease;
+}
+
+/* Ensure sider collapse trigger / bottom area stays white */
+.app-sider .ant-layout-sider-trigger {
+ background: #ffffff;
+ color: rgba(0, 0, 0, 0.85);
+}
+
+/* Button styling: primary buttons use black background, general buttons have dark text */
+.ant-btn {
+ color: rgba(0, 0, 0, 0.85);
+}
+
+.ant-btn-primary {
+ background: #000000;
+ border-color: #000000;
+ color: #ffffff;
+}
diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx
new file mode 100644
index 0000000..c727471
--- /dev/null
+++ b/src/layouts/index.tsx
@@ -0,0 +1,23 @@
+import { Outlet, useLocation } from 'umi';
+
+import PrimaryLayout from './PrimaryLayout';
+import PublicLayout from './PublicLayout';
+
+export default function LayoutWrapper() {
+ const { pathname } = useLocation();
+
+ // Simple rule: use PublicLayout for authentication/public routes
+ if (pathname.startsWith('/public') || pathname === '/login') {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
new file mode 100644
index 0000000..fd86458
--- /dev/null
+++ b/src/locales/en-US.json
@@ -0,0 +1,16 @@
+{
+ "welcome": "Welcome to Antd Admin",
+ "login.username": "Username",
+ "login.password": "Password",
+ "login.usernameRequired": "Please enter your username",
+ "login.passwordRequired": "Please enter your password",
+ "login.remember": "Remember me",
+ "login.submit": "Sign In",
+ "login.success": "Login success, redirecting...",
+ "login.themeLight": "Light",
+ "login.themeDark": "Dark",
+ "login.switchEnglish": "English",
+ "login.switchChinese": "Chinese",
+ "guide.i18n.example": "Example: add an entry to locale files and use it in pages:",
+ "guide.i18n.usage": "Usage in pages"
+}
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
deleted file mode 100644
index ae7f11d..0000000
--- a/src/locales/en/messages.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "Password": "Password",
- "/dashboard": "/dashboard",
- "Add Param": "Add Param",
- "Address": "Address",
- "Age": "Age",
- "Are you sure delete this record?": "Are you sure delete this record?",
- "Author": "Author",
- "Avatar": "Avatar",
- "Categories": "Categories",
- "Clear notifications": "Clear notifications",
- "Comments": "Comments",
- "Create": "Create",
- "Create User": "Create User",
- "CreateTime": "CreateTime",
- "Dark": "Dark",
- "Delete": "Delete",
- "Email": "Email",
- "Female": "Female",
- "Gender": "Gender",
- "Hi,": "Hi,",
- "Image": "Image",
- "Light": "Light",
- "Male": "Male",
- "Name": "Name",
- "NickName": "NickName",
- "Not Found": "Not Found",
- "Operation": "Operation",
- "Params": "Params",
- "Phone": "Phone",
- "Pick an address": "Pick an address",
- "Please pick an address": "Please pick an address",
- "Publised": "Publised",
- "Publish Date": "Publish Date",
- "Reset": "Reset",
- "Search": "Search",
- "Search Name": "Search Name",
- "Send": "Send",
- "Sign in": "Sign in",
- "Sign out": "Sign out",
- "Switch Theme": "Switch Theme",
- "Tags": "Tags",
- "The input is not valid E-mail!": "The input is not valid E-mail!",
- "The input is not valid phone!": "The input is not valid phone!",
- "Title": "Title",
- "Total {total} Items": "Total {total} Items",
- "Unpublished": "Unpublished",
- "Update": "Update",
- "Update User": "Update User",
- "Username": "Username",
- "Views": "Views",
- "Visibility": "Visibility",
- "You have viewed all notifications.": "You have viewed all notifications."
-}
\ No newline at end of file
diff --git a/src/locales/pt-br/messages.json b/src/locales/pt-br/messages.json
deleted file mode 100644
index ad7bedb..0000000
--- a/src/locales/pt-br/messages.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "Password": "Senha",
- "/dashboard": "/dashboard",
- "Add Param": "Add Parametro",
- "Address": "Endereço",
- "Age": "Ano",
- "Are you sure delete this record?": "Tem certeza de excluir este registro?",
- "Author": "Autor",
- "Avatar": "Avatar",
- "Categories": "Categorias",
- "Clear notifications": "limpar notificações",
- "Comments": "Comentarios",
- "Create": "Criar",
- "Create User": "Criar Usuário",
- "CreateTime": "CreateTime",
- "Dark": "Escuro",
- "Delete": "Deletar",
- "Email": "Email",
- "Female": "Feminino",
- "Gender": "Genero",
- "Hi,": "Olá,",
- "Image": "Imagem",
- "Light": "Claro",
- "Male": "masculino",
- "Name": "Nome",
- "NickName": "NickName",
- "Not Found": "Não Encontrado",
- "Operation": "Operation",
- "Params": "Parametros",
- "Phone": "Fone",
- "Pick an address": "Escolha um endereço",
- "Please pick an address": "Por favor, escolha um endereço",
- "Publised": "Publicado",
- "Publish Date": "Data de publicação",
- "Reset": "Reset",
- "Search": "procurar",
- "Search Name": "Search Name",
- "Send": "Enviar",
- "Sign in": "Sign in",
- "Sign out": "Sign out",
- "Switch Theme": "Trocar tema",
- "Tags": "Tags",
- "The input is not valid E-mail!": "Não é um E-mail valido!",
- "The input is not valid phone!": "Não é um telefone Valido!",
- "Title": "Titulo",
- "Total {total} Items": "Total {total} Items",
- "Unpublished": "Não publicado",
- "Update": "Atualizar",
- "Update User": "Atualizar Usuário",
- "Username": "Usuário",
- "Views": "visualizações",
- "Visibility": "Visibilidade",
- "You have viewed all notifications.": "Você visualizou todas as notificações."
-}
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
new file mode 100644
index 0000000..748c452
--- /dev/null
+++ b/src/locales/zh-CN.json
@@ -0,0 +1,16 @@
+{
+ "welcome": "欢迎使用 Antd Admin",
+ "login.username": "用户名",
+ "login.password": "密码",
+ "login.usernameRequired": "请输入用户名",
+ "login.passwordRequired": "请输入密码",
+ "login.remember": "记住我",
+ "login.submit": "登录",
+ "login.success": "登录成功",
+ "login.themeLight": "浅色",
+ "login.themeDark": "暗色",
+ "login.switchEnglish": "English",
+ "login.switchChinese": "中文",
+ "guide.i18n.example": "示例:在翻译文件中增加条目并在页面使用:",
+ "guide.i18n.usage": "页面中可使用"
+}
diff --git a/src/locales/zh/messages.json b/src/locales/zh/messages.json
deleted file mode 100644
index a72797f..0000000
--- a/src/locales/zh/messages.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "Password": "密码",
- "/dashboard": "/zh/dashboard",
- "Add Param": "添加参数",
- "Address": "地址",
- "Age": "年龄",
- "Are you sure delete this record?": "您确定要删除这条记录吗?",
- "Author": "作者",
- "Avatar": "头像",
- "Categories": "类别",
- "Clear notifications": "清空消息",
- "Comments": "评论数",
- "Create": "创建",
- "Create User": "创建用户",
- "CreateTime": "创建时间",
- "Dark": "暗",
- "Delete": "删除",
- "Email": "电子邮件",
- "Female": "女",
- "Gender": "性别",
- "Hi,": "你好,",
- "Image": "图像",
- "Light": "明",
- "Male": "男性",
- "Name": "名字",
- "NickName": "昵称",
- "Not Found": "未找到",
- "Operation": "操作",
- "Params": "参数",
- "Phone": "电话",
- "Pick an address": "选择地址",
- "Please pick an address": "选择地址",
- "Publised": "已发布",
- "Publish Date": "发布日期",
- "Reset": "重置",
- "Search": "搜索",
- "Search Name": "搜索名字",
- "Send": "发送",
- "Sign in": "登录",
- "Sign out": "退出登录",
- "Switch Theme": "切换主题",
- "Tags": "标签",
- "The input is not valid E-mail!": "输入的电子邮件无效!",
- "The input is not valid phone!": "输入无效的手机!",
- "Title": "标题",
- "Total {total} Items": "总共 {total} 条记录",
- "Unpublished": "未发布",
- "Update": "更新",
- "Update User": "更新用户",
- "Username": "用户名",
- "Views": "浏览数",
- "Visibility": "可见性",
- "You have viewed all notifications.": "您已查看所有通知"
-}
\ No newline at end of file
diff --git a/src/models/app.js b/src/models/app.js
deleted file mode 100644
index 1c4d6bf..0000000
--- a/src/models/app.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import { history } from 'umi'
-import { stringify } from 'qs'
-import store from 'store'
-import { pathToRegexp } from 'path-to-regexp'
-import { ROLE_TYPE } from 'utils/constant'
-import { queryLayout } from 'utils'
-import { CANCEL_REQUEST_MESSAGE } from 'utils/constant'
-import api from 'api'
-import config from 'config'
-
-const { queryRouteList, logoutUser, queryUserInfo } = api
-
-const goDashboard = () => {
- if (pathToRegexp(['/', '/login']).exec(window.location.pathname)) {
- history.push({
- pathname: '/dashboard',
- })
- }
-}
-
-const appModel = {
- namespace: 'app',
- state: {
- routeList: [
- {
- id: '1',
- icon: 'laptop',
- name: 'Dashboard',
- zhName: '仪表盘',
- router: '/dashboard',
- },
- ],
- locationPathname: '',
- locationQuery: {},
- theme: store.get('theme') || 'light',
- collapsed: store.get('collapsed') || false,
- notifications: [
- {
- title: 'New User is registered.',
- date: new Date(Date.now() - 10000000),
- },
- {
- title: 'Application has been approved.',
- date: new Date(Date.now() - 50000000),
- },
- ],
- },
- subscriptions: {
- setup({ dispatch }) {
- dispatch({ type: 'query' })
- },
- setupHistory({ dispatch, history }) {
- history.listen(location => {
- dispatch({
- type: 'updateState',
- payload: {
- locationPathname: location.pathname,
- locationQuery: location.query,
- },
- })
- })
- },
-
- setupRequestCancel({ history }) {
- history.listen(() => {
- const { cancelRequest = new Map() } = window
-
- cancelRequest.forEach((value, key) => {
- if (value.pathname !== window.location.pathname) {
- value.abort(CANCEL_REQUEST_MESSAGE)
- cancelRequest.delete(key)
- }
- })
- })
- },
- },
- effects: {
- *query({ payload }, { call, put, select }) {
- // store isInit to prevent query trigger by refresh
- const isInit = store.get('isInit')
- if (isInit) {
- goDashboard()
- return
- }
- const { locationPathname } = yield select(_ => _.app)
- const { success, user } = yield call(queryUserInfo, payload)
- if (success && user) {
- const { list } = yield call(queryRouteList)
- const { permissions } = user
- let routeList = list
- if (
- permissions.role === ROLE_TYPE.ADMIN ||
- permissions.role === ROLE_TYPE.DEVELOPER
- ) {
- permissions.visit = list.map(item => item.id)
- } else {
- routeList = list.filter(item => {
- const cases = [
- permissions.visit.includes(item.id),
- item.mpid
- ? permissions.visit.includes(item.mpid) || item.mpid === '-1'
- : true,
- item.bpid ? permissions.visit.includes(item.bpid) : true,
- ]
- return cases.every(_ => _)
- })
- }
- store.set('routeList', routeList)
- store.set('permissions', permissions)
- store.set('user', user)
- store.set('isInit', true)
- goDashboard()
- } else if (queryLayout(config.layouts, locationPathname) !== 'public') {
- history.push({
- pathname: '/login',
- search: stringify({
- from: locationPathname,
- }),
- })
- }
- },
-
- *signOut({ payload }, { call, put }) {
- const data = yield call(logoutUser)
- if (data.success) {
- store.set('routeList', [])
- store.set('permissions', { visit: [] })
- store.set('user', {})
- store.set('isInit', false)
- yield put({ type: 'query' })
- } else {
- throw data
- }
- },
- },
- reducers: {
- updateState(state, { payload }) {
- return {
- ...state,
- ...payload,
- }
- },
-
- handleThemeChange(state, { payload }) {
- store.set('theme', payload)
- state.theme = payload
- },
-
- handleCollapseChange(state, { payload }) {
- store.set('collapsed', payload)
- state.collapsed = payload
- },
-
- allNotificationsRead(state) {
- state.notifications = []
- },
- },
-}
-
-export default appModel
diff --git a/src/pages/404.less b/src/pages/404.less
deleted file mode 100644
index f5300b1..0000000
--- a/src/pages/404.less
+++ /dev/null
@@ -1,19 +0,0 @@
-.error {
- color: black;
- text-align: center;
- position: absolute;
- top: 30%;
- margin-top: -50px;
- left: 50%;
- margin-left: -100px;
- width: 200px;
-
- :global .anticon {
- font-size: 48px;
- margin-bottom: 16px;
- }
-
- h1 {
- font-family: cursive;
- }
-}
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index cff81d3..dfef767 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,15 +1,20 @@
-import React from 'react'
-import { FrownOutlined } from '@ant-design/icons'
-import { Page } from 'components'
-import styles from './404.less'
+import { Button, Result } from 'antd';
+import React from 'react';
+import { history } from 'umi';
-const Error = () => (
-
-
-
-
404 Not Found
-
-
-)
+const NotFound: React.FC = () => {
+ return (
+ history.push('/')}>
+ 返回首页
+
+ }
+ />
+ );
+};
-export default Error
+export default NotFound;
diff --git a/src/pages/admin/$id.tsx b/src/pages/admin/$id.tsx
new file mode 100644
index 0000000..68842a5
--- /dev/null
+++ b/src/pages/admin/$id.tsx
@@ -0,0 +1,143 @@
+import useIsMounted from '@/hooks/useIsMounted';
+import { getAdmin } from '@/services/admin';
+import type { Admin } from '@/types/admin';
+import {
+ Avatar,
+ Button,
+ Card,
+ Col,
+ Descriptions,
+ message,
+ Row,
+ Space,
+ Spin,
+ Tag,
+} from 'antd';
+
+import { useEffect, useState } from 'react';
+import { useParams } from 'umi';
+
+export default function AdminDetail() {
+ const params = useParams<{ id: string }>();
+ const { id } = params;
+ const [admin, setAdmin] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ try {
+ const resp = await getAdmin(String(id));
+ if (!isMounted()) return;
+ setAdmin((resp as import('@/types/admin').Admin) || null);
+ } catch (e) {
+ console.error(e);
+ } finally {
+ if (isMounted()) setLoading(false);
+ }
+ })();
+ }, [id, isMounted]);
+
+ if (loading)
+ return (
+
+
+
+ );
+ if (!admin) return 未找到管理员 ;
+ const perms = admin.permissions
+ ? String(admin.permissions)
+ .split(',')
+ .map((s: string) => s.trim())
+ .filter(Boolean)
+ : [];
+
+ return (
+
+
+
+
+
+
+
+ 用户名: {admin.username}
+
+
+ 姓名: {admin.real_name}
+
+
+ 昵称: {admin.nick_name}
+
+
+
+
+ {admin.status === 1 ? '已启用' : '已禁用'}
+
+ {admin.role || '未设置角色'}
+ {admin.department ? (
+ {admin.department}
+ ) : null}
+
+
+
+
+ message.info('重置密码已发送(模拟)')}
+ >
+ 重置密码
+
+
+ message.info(
+ `${admin.status === 1 ? '禁用' : '启用'} 操作已模拟`,
+ )
+ }
+ >
+ {admin.status === 1 ? '禁用账号' : '启用账号'}
+
+
+
+
+
+
+ 最后登录:
+
+
+ {(admin as any).lastLogin || '-'}
+
+
+ 创建者: {(admin as any).createdBy || '-'}
+
+
+
+
+
+ {admin.id}
+ {admin.mobile}
+ {admin.remark || ''}
+
+
+
+ {perms && perms.length ? (
+
+ {perms.map((p) => (
+
+ {p}
+
+ ))}
+
+ ) : (
+ 无权限
+ )}
+
+
+ );
+}
diff --git a/src/pages/admin/components/Filter.tsx b/src/pages/admin/components/Filter.tsx
new file mode 100644
index 0000000..883c26b
--- /dev/null
+++ b/src/pages/admin/components/Filter.tsx
@@ -0,0 +1,17 @@
+import { Button, Space } from 'antd';
+
+type Props = {
+ onAdd: () => void;
+};
+
+export default function Filter({ onAdd }: Props) {
+ return (
+
+
+
+ 新增管理员
+
+
+
+ );
+}
diff --git a/src/pages/admin/components/List.tsx b/src/pages/admin/components/List.tsx
new file mode 100644
index 0000000..f9d3c4e
--- /dev/null
+++ b/src/pages/admin/components/List.tsx
@@ -0,0 +1,67 @@
+import DropOption from '@/components/DropOption';
+import { Avatar, Table } from 'antd';
+
+import { Admin } from '@/types/admin';
+
+type Props = {
+ data: Admin[];
+ onEdit: (item: Admin) => void;
+ onDelete: (id: number) => void;
+ onDetail: (item: Admin) => void;
+};
+
+export default function AdminList({ data, onEdit, onDetail, onDelete }: Props) {
+ const columns = [
+ {
+ title: '头像',
+ dataIndex: 'avatar',
+ render: (_: unknown, record: Admin) => ,
+ },
+ {
+ title: '用户名',
+ dataIndex: 'username',
+ render: (_: unknown, record: Admin) => (
+ onDetail(record)}>{record.username}
+ ),
+ },
+ {
+ title: '姓名',
+ dataIndex: 'real_name',
+ },
+ { title: '手机', dataIndex: 'mobile' },
+ {
+ title: '权限',
+ dataIndex: 'permissions',
+ render: (p: string[] | string | undefined) => p || '',
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ render: (s: number) => (s === 1 ? '已启用' : '已禁用'),
+ },
+ {
+ title: '操作',
+ render: (_: unknown, record: Admin) => (
+ {
+ if (key === 'edit') onEdit(record);
+ if (key === 'delete') onDelete(record.id);
+ }}
+ >
+ ),
+ },
+ ];
+
+ return (
+
+ );
+}
diff --git a/src/pages/admin/components/Modal.tsx b/src/pages/admin/components/Modal.tsx
new file mode 100644
index 0000000..8e6621a
--- /dev/null
+++ b/src/pages/admin/components/Modal.tsx
@@ -0,0 +1,94 @@
+import type { Admin } from '@/types/admin';
+import { Form, Input, Modal, Select } from 'antd';
+import { useEffect } from 'react';
+
+const layout = { labelCol: { span: 6 }, wrapperCol: { span: 14 } };
+
+type Props = {
+ open: boolean;
+ item?: Admin;
+ routes: Array<{ id: string; name?: string; route: string }>;
+ onCancel: () => void;
+ onOk: (values: Record) => void;
+};
+
+export default function AdminModal({
+ open,
+ item,
+ routes,
+ onCancel,
+ onOk,
+}: Props) {
+ const [form] = Form.useForm();
+
+ useEffect(() => {
+ if (!open) return;
+ const perms =
+ item && item.permissions
+ ? typeof item.permissions === 'string'
+ ? (item.permissions as string)
+ .split(',')
+ .map((s: string) => s.trim())
+ .filter(Boolean)
+ : (item.permissions as string[])
+ : [];
+ form.setFieldsValue({
+ username: item?.username,
+ real_name: item?.real_name,
+ nick_name: item?.nick_name,
+ mobile: item?.mobile,
+ permissions: perms,
+ });
+ }, [open, item, form]);
+
+ const handleOk = async () => {
+ try {
+ const values = await form.validateFields();
+ onOk(values);
+ } catch (e) {
+ // validation failed
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ label: r.name || r.route,
+ value: r.route,
+ }))}
+ />
+
+
+
+ );
+}
diff --git a/src/pages/admin/list.tsx b/src/pages/admin/list.tsx
new file mode 100644
index 0000000..52919b8
--- /dev/null
+++ b/src/pages/admin/list.tsx
@@ -0,0 +1,108 @@
+import { Card, message } from 'antd';
+import { startTransition, useDeferredValue, useEffect, useState } from 'react';
+
+import useIsMounted from '@/hooks/useIsMounted';
+import adminService from '@/services/admin';
+import { Admin } from '@/types/admin';
+import api from '@/utils/request';
+import Filter from './components/Filter';
+
+import AdminList from './components/List';
+import AdminModal from './components/Modal';
+
+export default function ListPage() {
+ const [data, setData] = useState([]);
+ const [routes, setRoutes] = useState<
+ Array<{ id: string; name?: string; route: string }>
+ >([]);
+ const [modalOpen, setModalOpen] = useState(false);
+ const [currentItem, setCurrentItem] = useState(undefined);
+
+ const load = async () => {
+ try {
+ const resp = (await adminService.listAdmins()) as { data?: Admin[] };
+ startTransition(() => {
+ setData(resp?.data || []);
+ });
+ } catch (e) {
+ message.warning('无法加载管理员列表,显示空数据');
+ }
+ };
+
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ try {
+ const r = await api('/routes');
+ if (isMounted()) startTransition(() => setRoutes(r as any));
+ } catch (e) {
+ // ignore
+ }
+ await load();
+ })();
+ }, [isMounted]);
+
+ const deferredData = useDeferredValue(data);
+
+ const handleAdd = () => {
+ setCurrentItem(undefined);
+ setModalOpen(true);
+ };
+
+ const handleEdit = (item: Admin) => {
+ setCurrentItem(item);
+ setModalOpen(true);
+ };
+
+ const handleDetail = (item: Admin) => {
+ window.open(`/admin/${item.id}`, '_blank');
+ };
+
+ const handleDelete = async (id: number) => {
+ try {
+ await adminService.deleteAdmin(String(id));
+ await load();
+ message.success('删除成功');
+ } catch (e) {
+ console.error(e);
+ }
+ };
+
+ const handleSave = async (values: Record) => {
+ try {
+ const body = { ...values };
+ if (Array.isArray(body.permissions))
+ body.permissions = body.permissions.join(',');
+ if (currentItem) {
+ await adminService.updateAdmin(String(currentItem.id), body);
+ message.success('更新成功');
+ } else {
+ await adminService.createAdmin(body);
+ message.success('新增成功');
+ }
+ setModalOpen(false);
+ await load();
+ } catch (e) {
+ console.error(e);
+ }
+ };
+
+ return (
+
+
+
+ setModalOpen(false)}
+ onOk={handleSave}
+ />
+
+ );
+}
diff --git a/src/pages/ai/aiConfig.ts b/src/pages/ai/aiConfig.ts
new file mode 100644
index 0000000..74c5d16
--- /dev/null
+++ b/src/pages/ai/aiConfig.ts
@@ -0,0 +1,116 @@
+export type ProviderKey = 'openai' | 'qwen' | 'deepseek' | 'custom';
+
+export type ProviderConfig = {
+ apiKey: string;
+ baseURL: string;
+ model: string;
+};
+
+export const PROVIDER_OPTIONS: Array<{ value: ProviderKey; label: string }> = [
+ { value: 'deepseek', label: 'DeepSeek' },
+ { value: 'qwen', label: '千问' },
+ { value: 'openai', label: 'OpenAI' },
+ { value: 'custom', label: '其他' },
+];
+
+export type ConfigState = Record;
+
+export type AISettings = {
+ activeProvider: ProviderKey;
+ providers: ConfigState;
+};
+
+export const AI_SETTINGS_STORAGE_KEY = 'ai-provider-settings';
+
+export const DEFAULT_CONFIG: ConfigState = {
+ openai: {
+ apiKey: '',
+ baseURL: 'https://api.openai.com/v1',
+ model: 'gpt-4o-mini',
+ },
+ qwen: {
+ apiKey: '',
+ baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
+ model: 'qwen-plus',
+ },
+ deepseek: {
+ apiKey: '',
+ baseURL: 'https://api.deepseek.com/v1',
+ model: 'deepseek-chat',
+ },
+ custom: {
+ apiKey: '',
+ baseURL: '',
+ model: '',
+ },
+};
+
+export const DEFAULT_SETTINGS: AISettings = {
+ activeProvider: 'deepseek',
+ providers: DEFAULT_CONFIG,
+};
+
+export function loadAISettings(): AISettings {
+ try {
+ const raw = localStorage.getItem(AI_SETTINGS_STORAGE_KEY);
+ if (!raw) return DEFAULT_SETTINGS;
+
+ const parsed = JSON.parse(raw) as Partial;
+ const activeProvider = parsed.activeProvider;
+ return {
+ activeProvider:
+ activeProvider === 'openai' ||
+ activeProvider === 'qwen' ||
+ activeProvider === 'deepseek' ||
+ activeProvider === 'custom'
+ ? activeProvider
+ : 'openai',
+ providers: {
+ ...DEFAULT_CONFIG,
+ ...(parsed.providers || {}),
+ },
+ };
+ } catch (error) {
+ return DEFAULT_SETTINGS;
+ }
+}
+
+export function saveAISettings(settings: AISettings) {
+ localStorage.setItem(AI_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
+}
+
+export function loadAIConfig(): ConfigState {
+ return loadAISettings().providers;
+}
+
+export function saveAIConfig(config: ConfigState) {
+ const settings = loadAISettings();
+ saveAISettings({
+ ...settings,
+ providers: config,
+ });
+}
+
+export function loadActiveProvider(): ProviderKey {
+ return loadAISettings().activeProvider;
+}
+
+export function saveActiveProvider(provider: ProviderKey) {
+ const settings = loadAISettings();
+ saveAISettings({
+ ...settings,
+ activeProvider: provider,
+ });
+}
+
+export function getProviderLabel(provider: ProviderKey) {
+ const item = PROVIDER_OPTIONS.find((option) => option.value === provider);
+ return item?.label || '其他';
+}
+
+export function getProviderSummary(
+ provider: ProviderKey,
+ config: ProviderConfig,
+) {
+ return `${getProviderLabel(provider)} / ${config.model || '未配置模型'}`;
+}
diff --git a/src/pages/ai/chat.tsx b/src/pages/ai/chat.tsx
new file mode 100644
index 0000000..db0f635
--- /dev/null
+++ b/src/pages/ai/chat.tsx
@@ -0,0 +1,637 @@
+import { RobotOutlined } from '@ant-design/icons';
+import { Bubble, Conversations, Prompts, Sender, Welcome } from '@ant-design/x';
+import { type AbstractXRequestClass, XRequest } from '@ant-design/x-sdk';
+import {
+ Button,
+ Card,
+ Input,
+ message,
+ Modal,
+ Select,
+ Space,
+ Typography,
+} from 'antd';
+import {
+ useDeferredValue,
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+
+import {
+ getProviderLabel,
+ getProviderSummary,
+ loadActiveProvider,
+ loadAIConfig,
+ type ProviderConfig,
+ type ProviderKey,
+} from './aiConfig';
+import AiModal from './components/Modal';
+import { mockReply, PROMPT_ITEMS } from './components/mockData';
+
+type MessageStatus = 'loading' | 'success';
+
+type ChatMessage = {
+ key: string;
+ role: 'user' | 'ai';
+ content: string;
+ status?: MessageStatus;
+};
+
+type SessionItem = {
+ key: string;
+ label: string;
+ messages: ChatMessage[];
+};
+
+type OpenAICompatResponse = {
+ choices?: Array<{
+ message?: {
+ content?: string | Array<{ type?: string; text?: string }>;
+ };
+ }>;
+ error?: {
+ message?: string;
+ };
+};
+
+function createId(prefix: string) {
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+}
+
+function isDirectExternalURL(url: string) {
+ return /^https?:\/\//i.test(url.trim());
+}
+
+function extractAIContent(chunk?: OpenAICompatResponse) {
+ const content = chunk?.choices?.[0]?.message?.content;
+ if (typeof content === 'string') return content;
+ if (Array.isArray(content)) {
+ return content
+ .map((item) => item?.text || '')
+ .filter(Boolean)
+ .join('\n');
+ }
+ return '';
+}
+
+function requestReplyByXSDK(params: {
+ message: string;
+ providerConfig: ProviderConfig;
+ onSuccess: (content: string) => void;
+ onError: (error: Error) => void;
+}): AbstractXRequestClass<
+ {
+ model: string;
+ messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>;
+ stream: boolean;
+ },
+ OpenAICompatResponse
+> {
+ const { message, providerConfig, onSuccess, onError } = params;
+ const baseURL = providerConfig.baseURL.replace(/\/+$/, '');
+ const request = XRequest<
+ {
+ model: string;
+ messages: Array<{
+ role: 'user' | 'assistant' | 'system';
+ content: string;
+ }>;
+ stream: boolean;
+ },
+ OpenAICompatResponse
+ >(`${baseURL}/chat/completions`, {
+ manual: true,
+ timeout: 30000,
+ streamTimeout: 90000,
+ retryInterval: 1500,
+ retryTimes: 1,
+ headers: {
+ Authorization: `Bearer ${providerConfig.apiKey}`,
+ },
+ callbacks: {
+ onSuccess: (chunks) => {
+ const last = chunks[chunks.length - 1];
+ const content = extractAIContent(last);
+ if (content) {
+ onSuccess(content);
+ return;
+ }
+ onError(
+ new Error(last?.error?.message || '响应为空,请检查模型返回格式。'),
+ );
+ },
+ onError: (error) => {
+ onError(error);
+ },
+ },
+ });
+
+ request.run({
+ model: providerConfig.model,
+ stream: false,
+ messages: [{ role: 'user', content: message }],
+ });
+
+ return request;
+}
+
+function createWelcomeMessage(): ChatMessage {
+ return {
+ key: createId('ai'),
+ role: 'ai',
+ content: '你好,我是示例 AI 助手。你可以直接提问,或点击下方提示词。',
+ status: 'success',
+ };
+}
+
+function createSession(label: string): SessionItem {
+ return {
+ key: createId('session'),
+ label,
+ messages: [createWelcomeMessage()],
+ };
+}
+
+export default function AIChatPage() {
+ const [loading, setLoading] = useState(false);
+ const [isMobile, setIsMobile] = useState(
+ typeof window !== 'undefined' ? window.innerWidth <= 768 : false,
+ );
+ const [sessions, setSessions] = useState([
+ createSession('默认会话'),
+ createSession('React 问答'),
+ ]);
+ const [activeKey, setActiveKey] = useState(() => sessions[0].key);
+ const [provider, setProvider] = useState('openai');
+ const [providerConfig, setProviderConfig] = useState({
+ apiKey: '',
+ baseURL: '',
+ model: '',
+ });
+ const [configOpen, setConfigOpen] = useState(false);
+ const [senderKey, setSenderKey] = useState(0);
+ const currentRequestRef = useRef;
+ stream: boolean;
+ },
+ OpenAICompatResponse
+ > | null>(null);
+ const mockTimerRef = useRef(null);
+ const currentMessageRef = useRef<{
+ sessionKey: string;
+ aiKey: string;
+ } | null>(null);
+ const securityWarnedRef = useRef(false);
+
+ const activeSession = useMemo(
+ () => sessions.find((item) => item.key === activeKey) || sessions[0],
+ [activeKey, sessions],
+ );
+ const messages = activeSession?.messages || [];
+ const deferredMessages = useDeferredValue(messages);
+ const chatRegionId = useId();
+
+ useEffect(() => {
+ const activeProvider = loadActiveProvider();
+ const config = loadAIConfig();
+ setProvider(activeProvider);
+ setProviderConfig(config[activeProvider]);
+ }, []);
+
+ useEffect(() => {
+ const onResize = () => setIsMobile(window.innerWidth <= 768);
+ window.addEventListener('resize', onResize);
+ return () => window.removeEventListener('resize', onResize);
+ }, []);
+
+ const reloadProviderConfig = () => {
+ const activeProvider = loadActiveProvider();
+ const config = loadAIConfig();
+ setProvider(activeProvider);
+ setProviderConfig(config[activeProvider]);
+ };
+
+ const roles = useMemo(
+ () => ({
+ ai: {
+ placement: 'start' as const,
+ avatar: ,
+ typing: { effect: 'typing' as const, interval: 20, step: 3 },
+ },
+ user: {
+ placement: 'end' as const,
+ },
+ }),
+ [],
+ );
+
+ const sendMessage = (rawMessage: string) => {
+ const userText = rawMessage.trim();
+ if (!userText || loading || !activeSession) return;
+ // clear sender input immediately by remounting Sender
+ setSenderKey((k) => k + 1);
+ const useMock = !providerConfig.apiKey?.trim();
+ if (!useMock && (!providerConfig.baseURL || !providerConfig.model)) {
+ Modal.warning({
+ title: '配置不完整',
+ content: '已填写 API Key,但 Base URL 或 Model 为空,请补全后再发送。',
+ });
+ return;
+ }
+
+ if (
+ !useMock &&
+ isDirectExternalURL(providerConfig.baseURL) &&
+ providerConfig.apiKey &&
+ !securityWarnedRef.current
+ ) {
+ securityWarnedRef.current = true;
+ message.warning(
+ '当前为前端直连外部模型服务,API Key 将暴露在浏览器中。生产环境建议改为同域代理(如 /api/proxy/...)。',
+ );
+ }
+
+ const targetSessionKey = activeSession.key;
+ const userKey = createId('user');
+ const aiKey = createId('ai');
+
+ setLoading(true);
+ currentMessageRef.current = { sessionKey: targetSessionKey, aiKey };
+ setSessions((prev) =>
+ prev.map((session) =>
+ session.key === targetSessionKey
+ ? {
+ ...session,
+ messages: [
+ ...session.messages,
+ {
+ key: userKey,
+ role: 'user',
+ content: userText,
+ status: 'success',
+ },
+ {
+ key: aiKey,
+ role: 'ai',
+ content: '正在思考中...',
+ status: 'loading',
+ },
+ ],
+ }
+ : session,
+ ),
+ );
+
+ if (useMock) {
+ const content = mockReply(userText, provider, providerConfig);
+ mockTimerRef.current = window.setTimeout(() => {
+ setSessions((prev) =>
+ prev.map((session) =>
+ session.key === targetSessionKey
+ ? {
+ ...session,
+ messages: session.messages.map((item) =>
+ item.key === aiKey
+ ? { ...item, content, status: 'success' }
+ : item,
+ ),
+ }
+ : session,
+ ),
+ );
+ mockTimerRef.current = null;
+ currentMessageRef.current = null;
+ setLoading(false);
+ }, 450);
+ return;
+ }
+
+ currentRequestRef.current = requestReplyByXSDK({
+ message: userText,
+ providerConfig,
+ onSuccess: (content) => {
+ setSessions((prev) =>
+ prev.map((session) =>
+ session.key === targetSessionKey
+ ? {
+ ...session,
+ messages: session.messages.map((item) =>
+ item.key === aiKey
+ ? { ...item, content, status: 'success' }
+ : item,
+ ),
+ }
+ : session,
+ ),
+ );
+ currentRequestRef.current = null;
+ currentMessageRef.current = null;
+ setLoading(false);
+ },
+ onError: (error) => {
+ if (error.name === 'AbortError') {
+ currentRequestRef.current = null;
+ currentMessageRef.current = null;
+ setLoading(false);
+ return;
+ }
+
+ setSessions((prev) =>
+ prev.map((session) =>
+ session.key === targetSessionKey
+ ? {
+ ...session,
+ messages: session.messages.map((item) =>
+ item.key === aiKey
+ ? {
+ ...item,
+ content: `请求失败:${
+ error.message || '请检查 Key 配置。'
+ }`,
+ status: 'success',
+ }
+ : item,
+ ),
+ }
+ : session,
+ ),
+ );
+ currentRequestRef.current = null;
+ currentMessageRef.current = null;
+ setLoading(false);
+ },
+ });
+ };
+
+ const handleCancelRequest = () => {
+ const current = currentMessageRef.current;
+ if (current) {
+ setSessions((prev) =>
+ prev.map((session) =>
+ session.key === current.sessionKey
+ ? {
+ ...session,
+ messages: session.messages.map((item) =>
+ item.key === current.aiKey
+ ? { ...item, content: '已取消生成。', status: 'success' }
+ : item,
+ ),
+ }
+ : session,
+ ),
+ );
+ }
+
+ currentRequestRef.current?.abort();
+ currentRequestRef.current = null;
+ if (mockTimerRef.current) {
+ window.clearTimeout(mockTimerRef.current);
+ mockTimerRef.current = null;
+ }
+ currentMessageRef.current = null;
+ setLoading(false);
+ };
+
+ const handleCreateSession = () => {
+ const next = createSession(`新会话 ${sessions.length + 1}`);
+ setSessions((prev) => [next, ...prev]);
+ setActiveKey(next.key);
+ };
+
+ const handleRenameSession = (sessionKey: string) => {
+ const current = sessions.find((item) => item.key === sessionKey);
+ if (!current) return;
+
+ let nextLabel = current.label;
+
+ Modal.confirm({
+ title: '重命名会话',
+ content: (
+ {
+ nextLabel = event.target.value;
+ }}
+ onPressEnter={(event) => {
+ event.preventDefault();
+ }}
+ />
+ ),
+ okText: '确定',
+ cancelText: '取消',
+ onOk: () => {
+ const value = nextLabel.trim();
+ if (!value) {
+ message.warning('会话名称不能为空');
+ return Promise.reject();
+ }
+ setSessions((prev) =>
+ prev.map((item) =>
+ item.key === sessionKey ? { ...item, label: value } : item,
+ ),
+ );
+ return Promise.resolve();
+ },
+ });
+ };
+
+ const handleDeleteSession = (sessionKey: string) => {
+ if (sessions.length <= 1) {
+ message.warning('至少保留一个会话');
+ return;
+ }
+
+ Modal.confirm({
+ title: '删除会话',
+ content: '删除后不可恢复,确定删除该会话吗?',
+ okText: '删除',
+ okButtonProps: { danger: true },
+ cancelText: '取消',
+ onOk: () => {
+ const nextSessions = sessions.filter((item) => item.key !== sessionKey);
+ setSessions(nextSessions);
+
+ if (activeKey === sessionKey) {
+ setActiveKey(nextSessions[0].key);
+ setLoading(false);
+ }
+ },
+ });
+ };
+
+ return (
+
+
+ {!isMobile ? (
+
+ ({
+ key: item.key,
+ label: item.label,
+ }))}
+ activeKey={activeKey}
+ onActiveChange={(key) => setActiveKey(String(key))}
+ creation={{ label: '新建会话', onClick: handleCreateSession }}
+ menu={(conversation) => ({
+ items: [
+ { key: 'rename', label: '重命名' },
+ { key: 'delete', label: '删除' },
+ ],
+ onClick: ({ key }) => {
+ if (key === 'rename') handleRenameSession(conversation.key);
+ if (key === 'delete') handleDeleteSession(conversation.key);
+ },
+ })}
+ />
+
+ ) : (
+
+ setActiveKey(String(k))}
+ options={sessions.map((s) => ({ label: s.label, value: s.key }))}
+ style={{ width: '100%' }}
+ popupMatchSelectWidth
+ />
+
+ )}
+
+
+ }
+ title={activeSession?.label || 'AI 对话示例'}
+ description={`多会话切换示例(Conversations + Bubble.List + Sender)。当前:${getProviderSummary(
+ provider,
+ providerConfig,
+ )}`}
+ extra={
+
+ setConfigOpen(true)}>
+ 配置 Key({getProviderLabel(provider)})
+
+
+ }
+ />
+
+ 试试这些问题}
+ items={PROMPT_ITEMS}
+ wrap
+ onItemClick={({ data }) =>
+ sendMessage(
+ typeof data.label === 'string'
+ ? data.label
+ : String(data.description || '介绍这个页面'),
+ )
+ }
+ />
+
+ {/* main chat area with fixed-bottom input */}
+
+ {/* content area: give bottom padding equal to sender height to avoid overlap */}
+
+
+ {/* fixed sender at bottom with fixed height and internal scroll */}
+
+
+
+
+ {/* force textarea height and allow internal scroll when content overflows */}
+
+
+
+
+ {
+ setConfigOpen(false);
+ reloadProviderConfig();
+ }}
+ onSaved={(nextProvider, nextConfig) => {
+ setProvider(nextProvider);
+ setProviderConfig(nextConfig);
+ }}
+ />
+
+ );
+}
diff --git a/src/pages/ai/components/Modal.tsx b/src/pages/ai/components/Modal.tsx
new file mode 100644
index 0000000..7c3faa0
--- /dev/null
+++ b/src/pages/ai/components/Modal.tsx
@@ -0,0 +1,143 @@
+import {
+ Button,
+ Form,
+ Input,
+ Modal,
+ Select,
+ Space,
+ Typography,
+ message,
+} from 'antd';
+import { useEffect, useId } from 'react';
+
+import useGlobalSync from '@/hooks/useGlobalSync';
+import useGlobalStore from '@/store/useGlobalStore';
+import {
+ DEFAULT_CONFIG,
+ PROVIDER_OPTIONS,
+ type ProviderConfig,
+ type ProviderKey,
+} from '../aiConfig';
+
+type Props = {
+ open: boolean;
+ onClose: () => void;
+ onSaved?: (provider: ProviderKey, config: ProviderConfig) => void;
+};
+
+export default function AiModal({ open, onClose, onSaved }: Props) {
+ // use activeProvider from global store as the single source of truth
+ const aiConfig = useGlobalSync((s) => s.aiConfig);
+ const activeProvider = useGlobalSync((s) => s.activeProvider);
+ const setAiConfig = useGlobalStore.getState().setAiConfig;
+ const setActiveProvider = useGlobalStore.getState().setActiveProvider;
+ const [form] = Form.useForm();
+ const formId = useId();
+ const selectId = useId();
+
+ useEffect(() => {
+ if (!open) return;
+ form.setFieldsValue(aiConfig[activeProvider]);
+ }, [open, form, aiConfig, activeProvider]);
+
+ const handleSave = async () => {
+ const values = await form.validateFields();
+ const provider = useGlobalStore.getState().activeProvider;
+ const next = {
+ ...aiConfig,
+ [provider]: values,
+ };
+ setAiConfig(next);
+ onSaved?.(provider, next[provider]);
+ message.success('配置已保存到本地');
+ };
+
+ const handleReset = () => {
+ const provider = useGlobalStore.getState().activeProvider;
+ const nextValue = DEFAULT_CONFIG[provider];
+ const next = {
+ ...aiConfig,
+ [provider]: nextValue,
+ };
+ setAiConfig(next);
+ form.setFieldsValue(nextValue);
+ onSaved?.(provider, next[provider]);
+ message.success('当前服务商配置已重置');
+ };
+
+ const handleProviderChange = (provider: ProviderKey) => {
+ setActiveProvider(provider);
+ form.setFieldsValue(aiConfig[provider]);
+ onSaved?.(provider, aiConfig[provider]);
+ message.success('默认 Provider 已更新');
+ };
+
+ return (
+
+
+ 默认 Provider:
+ handleProviderChange(value as ProviderKey)}
+ options={PROVIDER_OPTIONS}
+ />
+
+
+ {/* 移除多标签页切换:只根据“默认 Provider”选择展示对应配置 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存配置
+
+ 重置当前
+ 关闭
+
+
+
+ );
+}
diff --git a/src/pages/ai/components/mockData.tsx b/src/pages/ai/components/mockData.tsx
new file mode 100644
index 0000000..5db8e92
--- /dev/null
+++ b/src/pages/ai/components/mockData.tsx
@@ -0,0 +1,38 @@
+import { BulbOutlined } from '@ant-design/icons';
+
+import type { ProviderConfig, ProviderKey } from '../aiConfig';
+
+export const PROMPT_ITEMS = [
+ {
+ key: 'prompt-1',
+ icon: ,
+ label: '介绍一下这个示例页',
+ description: '页面能力说明',
+ },
+ {
+ key: 'prompt-2',
+ icon: ,
+ label: '给我 3 个 React 性能优化建议',
+ description: '开发常用问题',
+ },
+];
+
+export function mockReply(
+ question: string,
+ _provider: ProviderKey,
+ _providerConfig: ProviderConfig,
+) {
+ const text = question.toLowerCase();
+ if (text.includes('react') && text.includes('性能')) {
+ return [
+ '1. 使用 memo/useMemo 减少不必要渲染。',
+ '2. 大列表使用虚拟滚动。',
+ '3. 拆分状态,避免顶层状态过大导致全树刷新。',
+ ].join('\n');
+ }
+
+ return [
+ '检测到未配置 API Key,当前使用本地模拟回复。',
+ '填写 Key 后会自动切换到真实模型请求。',
+ ].join('\n');
+}
diff --git a/src/pages/chart/ECharts/BubbleGradientComponent.js b/src/pages/chart/ECharts/BubbleGradientComponent.js
deleted file mode 100644
index a8b2328..0000000
--- a/src/pages/chart/ECharts/BubbleGradientComponent.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-
-const currentData = [
- [
- [28604, 77, 17096869, 'Australia', 1990],
- [31163, 77.4, 27662440, 'Canada', 1990],
- [1516, 68, 1154605773, 'China', 1990],
- [13670, 74.7, 10582082, 'Cuba', 1990],
- [28599, 75, 4986705, 'Finland', 1990],
- [29476, 77.1, 56943299, 'France', 1990],
- [31476, 75.4, 78958237, 'Germany', 1990],
- [28666, 78.1, 254830, 'Iceland', 1990],
- [1777, 57.7, 870601776, 'India', 1990],
- [29550, 79.1, 122249285, 'Japan', 1990],
- [2076, 67.9, 20194354, 'North Korea', 1990],
- [12087, 72, 42972254, 'South Korea', 1990],
- [24021, 75.4, 3397534, 'New Zealand', 1990],
- [43296, 76.8, 4240375, 'Norway', 1990],
- [10088, 70.8, 38195258, 'Poland', 1990],
- [19349, 69.6, 147568552, 'Russia', 1990],
- [10670, 67.3, 53994605, 'Turkey', 1990],
- [26424, 75.7, 57110117, 'United Kingdom', 1990],
- [37062, 75.4, 252847810, 'United States', 1990],
- ],
- [
- [44056, 81.8, 23968973, 'Australia', 2015],
- [43294, 81.7, 35939927, 'Canada', 2015],
- [13334, 76.9, 1376048943, 'China', 2015],
- [21291, 78.5, 11389562, 'Cuba', 2015],
- [38923, 80.8, 5503457, 'Finland', 2015],
- [37599, 81.9, 64395345, 'France', 2015],
- [44053, 81.1, 80688545, 'Germany', 2015],
- [42182, 82.8, 329425, 'Iceland', 2015],
- [5903, 66.8, 1311050527, 'India', 2015],
- [36162, 83.5, 126573481, 'Japan', 2015],
- [1390, 71.4, 25155317, 'North Korea', 2015],
- [34644, 80.7, 50293439, 'South Korea', 2015],
- [34186, 80.6, 4528526, 'New Zealand', 2015],
- [64304, 81.6, 5210967, 'Norway', 2015],
- [24787, 77.3, 38611794, 'Poland', 2015],
- [23038, 73.13, 143456918, 'Russia', 2015],
- [19360, 76.5, 78665830, 'Turkey', 2015],
- [38225, 81.4, 64715810, 'United Kingdom', 2015],
- [53354, 79.1, 321773631, 'United States', 2015],
- ],
-]
-
-const option = {
- backgroundColor: new echarts.graphic.RadialGradient(0.3, 0.3, 0.8, [
- {
- offset: 0,
- color: '#f7f8fa',
- },
- {
- offset: 1,
- color: '#cdd0d5',
- },
- ]),
- title: {
- text: '1990 与 2015 年各国家人均寿命与 GDP',
- },
- legend: {
- right: 10,
- data: ['1990', '2015'],
- },
- xAxis: {
- splitLine: {
- lineStyle: {
- type: 'dashed',
- },
- },
- },
- yAxis: {
- splitLine: {
- lineStyle: {
- type: 'dashed',
- },
- },
- scale: true,
- },
- series: [
- {
- name: '1990',
- data: currentData[0],
- type: 'scatter',
- symbolSize(data) {
- return Math.sqrt(data[2]) / 5e2
- },
- emphasis: {
- label: {
- show: true,
- formatter(param) {
- return param.data[3]
- },
- position: 'top',
- },
- },
- itemStyle: {
- shadowBlur: 10,
- shadowColor: 'rgba(120, 36, 50, 0.5)',
- shadowOffsetY: 5,
- color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [
- {
- offset: 0,
- color: 'rgb(251, 118, 123)',
- },
- {
- offset: 1,
- color: 'rgb(204, 46, 72)',
- },
- ]),
- },
- },
- {
- name: '2015',
- data: currentData[1],
- type: 'scatter',
- symbolSize(data) {
- return Math.sqrt(data[2]) / 5e2
- },
- emphasis: {
- label: {
- show: true,
- formatter(param) {
- return param.data[3]
- },
- position: 'top',
- },
- },
- itemStyle: {
- shadowBlur: 10,
- shadowColor: 'rgba(25, 100, 150, 0.5)',
- shadowOffsetY: 5,
- color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [
- {
- offset: 0,
- color: 'rgb(129, 227, 238)',
- },
- {
- offset: 1,
- color: 'rgb(25, 183, 207)',
- },
- ]),
- },
- },
- ],
-}
-
-const BubbleGradientComponent = () => {
- return (
-
- )
-}
-
-export default BubbleGradientComponent
diff --git a/src/pages/chart/ECharts/CalendarComponent.js b/src/pages/chart/ECharts/CalendarComponent.js
deleted file mode 100644
index 79dd50c..0000000
--- a/src/pages/chart/ECharts/CalendarComponent.js
+++ /dev/null
@@ -1,193 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-
-const CalendarComponent = () => {
- const getVirtulData = year => {
- year = year || '2017'
- let date = +echarts.number.parseDate(`${year}-01-01`)
- let end = +echarts.number.parseDate(`${+year + 1}-01-01`)
- let dayTime = 3600 * 24 * 1000
- let data = []
- for (let time = date; time < end; time += dayTime) {
- data.push([
- echarts.format.formatTime('yyyy-MM-dd', time),
- Math.floor(Math.random() * 1000),
- ])
- }
- return data
- }
-
- let graphData = [
- [1485878400000, 260],
- [1486137600000, 200],
- [1486569600000, 279],
- [1486915200000, 847],
- [1487347200000, 241],
- [1487779200000, 411],
- [1488124800000, 985],
- ]
-
- let links = graphData.map((item, idx) => {
- return {
- source: idx,
- target: idx + 1,
- }
- })
- links.pop()
-
- const option = {
- tooltip: {
- position: 'top',
- },
- visualMap: [
- {
- min: 0,
- max: 1000,
- calculable: true,
- seriesIndex: [2, 3, 4],
- orient: 'horizontal',
- left: '55%',
- bottom: 20,
- },
- {
- min: 0,
- max: 1000,
- inRange: {
- color: ['grey'],
- opacity: [0, 0.3],
- },
- controller: {
- inRange: {
- opacity: [0.3, 0.6],
- },
- outOfRange: {
- color: '#ccc',
- },
- },
- calculable: true,
- seriesIndex: [1],
- orient: 'horizontal',
- left: '10%',
- bottom: 20,
- },
- ],
-
- calendar: [
- {
- orient: 'vertical',
- yearLabel: {
- margin: 40,
- },
- monthLabel: {
- nameMap: 'cn',
- margin: 20,
- },
- dayLabel: {
- firstDay: 1,
- nameMap: 'cn',
- },
- cellSize: 40,
- range: '2017-02',
- },
- {
- orient: 'vertical',
- yearLabel: {
- margin: 40,
- },
- monthLabel: {
- margin: 20,
- },
- cellSize: 40,
- left: 460,
- range: '2017-01',
- },
- {
- orient: 'vertical',
- yearLabel: {
- margin: 40,
- },
- monthLabel: {
- margin: 20,
- },
- cellSize: 40,
- top: 350,
- range: '2017-03',
- },
- {
- orient: 'vertical',
- yearLabel: {
- margin: 40,
- },
- dayLabel: {
- firstDay: 1,
- nameMap: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
- },
- monthLabel: {
- nameMap: 'cn',
- margin: 20,
- },
- cellSize: 40,
- top: 350,
- left: 460,
- range: '2017-04',
- },
- ],
-
- series: [
- {
- type: 'graph',
- edgeSymbol: ['none', 'arrow'],
- coordinateSystem: 'calendar',
- links,
- symbolSize: 10,
- calendarIndex: 0,
- data: graphData,
- },
- {
- type: 'heatmap',
- coordinateSystem: 'calendar',
- data: getVirtulData(2017),
- },
- {
- type: 'effectScatter',
- coordinateSystem: 'calendar',
- calendarIndex: 1,
- symbolSize(val) {
- return val[1] / 40
- },
- data: getVirtulData(2017),
- },
- {
- type: 'scatter',
- coordinateSystem: 'calendar',
- calendarIndex: 2,
- symbolSize(val) {
- return val[1] / 60
- },
- data: getVirtulData(2017),
- },
- {
- type: 'heatmap',
- coordinateSystem: 'calendar',
- calendarIndex: 3,
- data: getVirtulData(2017),
- },
- ],
- }
-
- return (
-
-
- render a calendar-charts
-
-
-
- )
-}
-
-export default CalendarComponent
diff --git a/src/pages/chart/ECharts/ChartAPIComponent.js b/src/pages/chart/ECharts/ChartAPIComponent.js
deleted file mode 100644
index 1b18cdb..0000000
--- a/src/pages/chart/ECharts/ChartAPIComponent.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-class ChartAPIComponent extends React.Component {
- render() {
- const option = {
- title: {
- text: '漏斗图',
- subtext: '纯属虚构',
- },
- tooltip: {
- trigger: 'item',
- formatter: '{a} {b} : {c}%',
- },
- toolbox: {
- feature: {
- dataView: { readOnly: false },
- restore: {},
- saveAsImage: {},
- },
- },
- legend: {
- data: ['展现', '点击', '访问', '咨询', '订单'],
- },
- series: [
- {
- name: '预期',
- type: 'funnel',
- left: '10%',
- width: '80%',
- label: {
- formatter: '{b}预期',
- },
- emphasis: {
- label: {
- position: 'inside',
- formatter: '{b}预期: {c}%',
- },
- },
- labelLine: {
- show: false,
- },
- itemStyle: {
- opacity: 0.7,
- },
- data: [
- { value: 60, name: '访问' },
- { value: 40, name: '咨询' },
- { value: 20, name: '订单' },
- { value: 80, name: '点击' },
- { value: 100, name: '展现' },
- ],
- },
- {
- name: '实际',
- type: 'funnel',
- left: '10%',
- width: '80%',
- maxSize: '80%',
- label: {
- position: 'inside',
- formatter: '{c}%',
- color: '#fff',
- },
- emphasis: {
- label: {
- position: 'inside',
- formatter: '{b}实际: {c}%',
- },
- },
- itemStyle: {
- opacity: 0.5,
- borderColor: '#fff',
- borderWidth: 2,
- },
- data: [
- { value: 30, name: '访问' },
- { value: 10, name: '咨询' },
- { value: 5, name: '订单' },
- { value: 50, name: '点击' },
- { value: 80, name: '展现' },
- ],
- },
- ],
- }
-
- let code =
- ' { this.echarts_react = e; }} \n' +
- ' option={this.getOtion()} /> \n' +
- '\n' +
- '// use echarts API: http://echarts.baidu.com/api.html#echartsInstance' +
- 'this.echarts_react.getEchartsInstance().getDataURL();'
- return (
-
-
-
- {' '}
- use echarts API With getEchartsInstance() : (the
- API will return the echarts instance, then you can use any API of
- echarts.)
-
-
{
- this.echarts_react = e
- }}
- option={option}
- />
-
- {' '}
- code below: (echarts API list see:
- http://echarts.baidu.com/api.html#echartsInstance)
-
-
- {code}
-
-
-
- )
- }
-}
-
-export default ChartAPIComponent
diff --git a/src/pages/chart/ECharts/ChartShowLoadingComponent.js b/src/pages/chart/ECharts/ChartShowLoadingComponent.js
deleted file mode 100644
index ad53fc2..0000000
--- a/src/pages/chart/ECharts/ChartShowLoadingComponent.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-class ChartShowLoadingComponent extends React.Component {
- constructor() {
- super()
- this._t = null
- this.onChartReady = this.onChartReady.bind(this)
- }
- componentWillUnmount() {
- clearTimeout(this._t)
- }
-
- onChartReady(chart) {
- this._t = setTimeout(() => {
- chart.hideLoading()
- }, 3000)
- }
-
- render() {
- const getOtion = () => {
- const option = {
- title: {
- text: '基础雷达图',
- },
- tooltip: {},
- legend: {
- data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)'],
- },
- radar: {
- indicator: [
- { name: '销售(sales)', max: 7000 },
- { name: '管理(Administration)', max: 16000 },
- { name: '信息技术(Information Techology)', max: 30000 },
- { name: '客服(Customer Support)', max: 38000 },
- { name: '研发(Development)', max: 52000 },
- { name: '市场(Marketing)', max: 25000 },
- ],
- splitNumber: 5,
- alignTicks: false,
- },
- series: [
- {
- name: '预算 vs 开销(Budget vs spending)',
- type: 'radar',
- data: [
- {
- value: [4300, 10000, 28000, 35000, 50000, 19000],
- name: '预算分配(Allocated Budget)',
- },
- {
- value: [5000, 14000, 28000, 31000, 42000, 21000],
- name: '实际开销(Actual Spending)',
- },
- ],
- },
- ],
- }
- return option
- }
- const getLoadingOption = () => {
- const option = {
- text: '加载中...',
- color: '#4413c2',
- textColor: '#270240',
- maskColor: 'rgba(194, 88, 86, 0.3)',
- zlevel: 0,
- }
- return option
- }
-
- let code =
- 'onChartReady: function(chart) {\n' +
- " 'chart.hideLoading();\n" +
- '}\n\n' +
- ' '
-
- return (
-
-
-
- {' '}
- Chart loading With showLoading : (when chart
- ready, hide the loading mask.)
-
-
-
code below:
-
- {code}
-
-
-
- )
- }
-}
-
-export default ChartShowLoadingComponent
diff --git a/src/pages/chart/ECharts/ChartWithEventComponent.js b/src/pages/chart/ECharts/ChartWithEventComponent.js
deleted file mode 100644
index 92425bc..0000000
--- a/src/pages/chart/ECharts/ChartWithEventComponent.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-const ChartWithEventComponent = () => {
- const onChartReady = echart => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.log('echart is ready', echart)
- }
- }
- const onChartLegendselectchanged = (param, echart) => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.log(param, echart)
- }
- }
- const onChartClick = (param, echart) => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.log(param, echart)
- }
- }
- const getOtion = () => {
- const option = {
- title: {
- text: '某站点用户访问来源',
- subtext: '纯属虚构',
- x: 'center',
- },
- tooltip: {
- trigger: 'item',
- formatter: '{a} {b} : {c} ({d}%)',
- },
- legend: {
- orient: 'vertical',
- left: 'left',
- data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'],
- },
- series: [
- {
- name: '访问来源',
- type: 'pie',
- radius: '55%',
- center: ['50%', '60%'],
- data: [
- { value: 335, name: '直接访问' },
- { value: 310, name: '邮件营销' },
- { value: 234, name: '联盟广告' },
- { value: 135, name: '视频广告' },
- { value: 1548, name: '搜索引擎' },
- ],
- itemStyle: {},
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)',
- },
- },
- },
- ],
- }
- return option
- }
-
- let onEvents = {
- click: onChartClick,
- legendselectchanged: onChartLegendselectchanged,
- }
- let code =
- 'let onEvents = {\n' +
- " 'click': onChartClick,\n" +
- " 'legendselectchanged': onChartLegendselectchanged\n" +
- '}\n\n' +
- ' '
-
- return (
-
-
-
- {' '}
- Chart With event onEvents : (Click the chart, and
- watch the console)
-
-
-
code below:
-
- {code}
-
-
-
- )
-}
-
-export default ChartWithEventComponent
diff --git a/src/pages/chart/ECharts/DynamicChartComponent.js b/src/pages/chart/ECharts/DynamicChartComponent.js
deleted file mode 100644
index cf36844..0000000
--- a/src/pages/chart/ECharts/DynamicChartComponent.js
+++ /dev/null
@@ -1,214 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-class DynamicChartComponent extends React.Component {
- constructor(props) {
- super(props)
- this.echartsRef = React.createRef()
- this.timeTicket = null
- this.count = 51
-
- const option = {
- title: {
- text: 'Hello Echarts-for-react.',
- },
- tooltip: {
- trigger: 'axis',
- },
- legend: {
- data: ['最新成交价', '预购队列'],
- },
- toolbox: {
- show: true,
- feature: {
- dataView: { readOnly: false },
- restore: {},
- saveAsImage: {},
- },
- },
- grid: {
- top: 60,
- left: 30,
- right: 60,
- bottom: 30,
- },
- dataZoom: {
- show: false,
- start: 0,
- end: 100,
- },
- visualMap: {
- show: false,
- min: 0,
- max: 1000,
- color: [
- '#BE002F',
- '#F20C00',
- '#F00056',
- '#FF2D51',
- '#FF2121',
- '#FF4C00',
- '#FF7500',
- '#FF8936',
- '#FFA400',
- '#F0C239',
- '#FFF143',
- '#FAFF72',
- '#C9DD22',
- '#AFDD22',
- '#9ED900',
- '#00E500',
- '#0EB83A',
- '#0AA344',
- '#0C8918',
- '#057748',
- '#177CB0',
- ],
- },
- xAxis: [
- {
- type: 'category',
- boundaryGap: true,
- data: (function() {
- let now = new Date()
- let res = []
- let len = 50
- while (len--) {
- res.unshift(now.toLocaleTimeString().replace(/^\D*/, ''))
- now = new Date(now - 2000)
- }
- return res
- })(),
- },
- {
- type: 'category',
- boundaryGap: true,
- data: (function() {
- let res = []
- let len = 50
- while (len--) {
- res.push(50 - len + 1)
- }
- return res
- })(),
- },
- ],
- yAxis: [
- {
- type: 'value',
- scale: true,
- name: '价格',
- max: 20,
- min: 0,
- alignTicks: false,
- boundaryGap: [0.2, 0.2],
- },
- {
- type: 'value',
- scale: true,
- name: '预购量',
- max: 1200,
- min: 0,
- alignTicks: false,
- boundaryGap: [0.2, 0.2],
- },
- ],
- series: [
- {
- name: '预购队列',
- type: 'bar',
- xAxisIndex: 1,
- yAxisIndex: 1,
- itemStyle: {
- borderRadius: 4,
- },
- animationEasing: 'elasticOut',
- animationDelay(idx) {
- return idx * 10
- },
- animationDelayUpdate(idx) {
- return idx * 10
- },
- data: (function() {
- let res = []
- let len = 50
- while (len--) {
- res.push(Math.round(Math.random() * 1000))
- }
- return res
- })(),
- },
- {
- name: '最新成交价',
- type: 'line',
- data: (function() {
- let res = []
- let len = 0
- while (len < 50) {
- res.push((Math.random() * 10 + 5).toFixed(1) - 0)
- len++
- }
- return res
- })(),
- },
- ],
- }
-
- this.state = {
- option,
- }
-
- this.fetchNewDate = this.fetchNewDate.bind(this)
- }
-
- fetchNewDate() {
- let axisData = new Date().toLocaleTimeString().replace(/^\D*/, '')
- let { option } = this.state
- option.title.text = `Hello Echarts-for-react.${new Date().getSeconds()}`
- let data0 = option.series[0].data
- let data1 = option.series[1].data
- data0.shift()
- data0.push(Math.round(Math.random() * 1000))
- data1.shift()
- data1.push((Math.random() * 10 + 5).toFixed(1) - 0)
-
- option.xAxis[0].data.shift()
- option.xAxis[0].data.push(axisData)
- option.xAxis[1].data.shift()
- option.xAxis[1].data.push((this.count += 1))
- this.setState({ option })
- }
-
- componentDidMount() {
- if (this.timeTicket) {
- clearInterval(this.timeTicket)
- }
- this.timeTicket = setInterval(this.fetchNewDate, 1000)
- }
-
- componentWillUnmount() {
- if (this.timeTicket) {
- clearInterval(this.timeTicket)
- }
- }
-
- render() {
- let code =
- " \n'
- return (
-
-
-
use React state to render dynamic chart
-
-
code below: use state of react to render dynamic chart
-
- {code}
-
-
-
- )
- }
-}
-
-export default DynamicChartComponent
diff --git a/src/pages/chart/ECharts/EchartsComponent.js b/src/pages/chart/ECharts/EchartsComponent.js
deleted file mode 100644
index 58d6bed..0000000
--- a/src/pages/chart/ECharts/EchartsComponent.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-
-import SimpleChartComponent from './SimpleChartComponent'
-import ChartWithEventComponent from './ChartWithEventComponent'
-import ThemeChartComponent from './ThemeChartComponent'
-import ChartShowLoadingComponent from './ChartShowLoadingComponent'
-import ChartAPIComponent from './ChartAPIComponent'
-import DynamicChartComponent from './DynamicChartComponent'
-
-// removed MapChartComponent and AirportCoordComponent
-import CalendarComponent from './CalendarComponent'
-import GaugeComponent from './GaugeComponent'
-import GCalendarComponent from './GCalendarComponent'
-import GraphComponent from './GraphComponent'
-import LunarCalendarComponent from './LunarCalendarComponent'
-import TreemapComponent from './TreemapComponent'
-import LiquidfillComponent from './LiquidfillComponent'
-import BubbleGradientComponent from './BubbleGradientComponent'
-import TransparentBar3DComPonent from './TransparentBar3DComPonent'
-
-const EchartsComponent = ({ type }) => {
- if (type === 'simple') return
- if (type === 'loading') return
- if (type === 'api') return
- if (type === 'events') return
- if (type === 'theme') return
- if (type === 'dynamic') return
- // map and airport chart types removed
- if (type === 'graph') return
- if (type === 'calendar') return
- if (type === 'treemap') return
- if (type === 'gauge') return
- if (type === 'gcalendar') return
- if (type === 'lunar') return
- if (type === 'liquid') return
- if (type === 'BubbleGradientComponent') return
- if (type === 'TransparentBar3DComPonent') return
- return
-}
-
-EchartsComponent.propTypes = {
- type: PropTypes.string,
-}
-
-export default EchartsComponent
diff --git a/src/pages/chart/ECharts/GCalendarComponent.js b/src/pages/chart/ECharts/GCalendarComponent.js
deleted file mode 100644
index da81e14..0000000
--- a/src/pages/chart/ECharts/GCalendarComponent.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-
-const GCalendarComponent = () => {
- const getVirtulData = year => {
- year = year || '2017'
- let date = +echarts.number.parseDate(`${year}-01-01`)
- let end = +echarts.number.parseDate(`${+year + 1}-01-01`)
- let dayTime = 3600 * 24 * 1000
- let data = []
- for (let time = date; time < end; time += dayTime) {
- data.push([
- echarts.format.formatTime('yyyy-MM-dd', time),
- Math.floor(Math.random() * 1000),
- ])
- }
- return data
- }
-
- const option = {
- tooltip: {
- position: 'top',
- },
- visualMap: {
- min: 0,
- max: 1000,
- calculable: true,
- orient: 'horizontal',
- left: 'center',
- top: 'top',
- },
-
- calendar: [
- {
- range: '2017',
- cellSize: ['auto', 20],
- },
- {
- top: 260,
- range: '2016',
- cellSize: ['auto', 20],
- },
- ],
-
- series: [
- {
- type: 'heatmap',
- coordinateSystem: 'calendar',
- calendarIndex: 0,
- data: getVirtulData(2017),
- },
- {
- type: 'heatmap',
- coordinateSystem: 'calendar',
- calendarIndex: 1,
- data: getVirtulData(2016),
- },
- ],
- }
-
- return (
-
-
- render a calendar like github commit history.
-
-
-
- )
-}
-
-export default GCalendarComponent
diff --git a/src/pages/chart/ECharts/GaugeComponent.js b/src/pages/chart/ECharts/GaugeComponent.js
deleted file mode 100644
index 99f37e6..0000000
--- a/src/pages/chart/ECharts/GaugeComponent.js
+++ /dev/null
@@ -1,354 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-class GaugeComponent extends React.Component {
- constructor() {
- super()
- const option = {
- backgroundColor: '#1b1b1b',
- tooltip: {
- formatter: '{a} {c} {b}',
- },
- toolbox: {
- show: true,
- feature: {
- mark: { show: true },
- restore: { show: true },
- saveAsImage: { show: true },
- },
- },
- series: [
- {
- name: '速度',
- type: 'gauge',
- min: 0,
- max: 220,
- splitNumber: 11,
- radius: '50%',
- axisLine: {
- // 坐标轴线
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: [[0.09, 'lime'], [0.82, '#1e90ff'], [1, '#ff4500']],
- width: 3,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- axisLabel: {
- // 坐标轴小标记
- fontWeight: 'bolder',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- axisTick: {
- // 坐标轴小标记
- length: 15, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: 'auto',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- splitLine: {
- // 分隔线
- length: 25, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle(详见lineStyle)控制线条样式
- width: 3,
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- pointer: {
- // 分隔线
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- },
- title: {
- textStyle: {
- // 其余属性默认使用全局文本样式,详见TEXTSTYLE
- fontWeight: 'bolder',
- fontSize: 20,
- fontStyle: 'italic',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- detail: {
- backgroundColor: 'rgba(30,144,255,0.8)',
- borderWidth: 1,
- borderColor: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- offsetCenter: [0, '50%'], // x, y,单位px
- // 其余属性默认使用全局文本样式,详见TEXTSTYLE
- fontWeight: 'bolder',
- color: '#fff',
- },
- data: [{ value: 40, name: 'km/h' }],
- },
- {
- name: '转速',
- type: 'gauge',
- center: ['25%', '55%'], // 默认全局居中
- radius: '30%',
- min: 0,
- max: 7,
- endAngle: 45,
- splitNumber: 7,
- axisLine: {
- // 坐标轴线
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: [[0.29, 'lime'], [0.86, '#1e90ff'], [1, '#ff4500']],
- width: 2,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- axisLabel: {
- // 坐标轴小标记
- fontWeight: 'bolder',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- axisTick: {
- // 坐标轴小标记
- length: 12, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: 'auto',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- splitLine: {
- // 分隔线
- length: 20, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle(详见lineStyle)控制线条样式
- width: 3,
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- pointer: {
- width: 5,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- },
- title: {
- offsetCenter: [0, '-30%'], // x, y,单位px
- textStyle: {
- // 其余属性默认使用全局文本样式,详见TEXTSTYLE
- fontWeight: 'bolder',
- fontStyle: 'italic',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- detail: {
- // backgroundColor: 'rgba(30,144,255,0.8)',
- // borderWidth: 1,
- borderColor: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- width: 80,
- height: 30,
- offsetCenter: [25, '20%'], // x, y,单位px
- // 其余属性默认使用全局文本样式,详见TEXTSTYLE
- fontWeight: 'bolder',
- color: '#fff',
- },
- data: [{ value: 1.5, name: 'x1000 r/min' }],
- },
- {
- name: '油表',
- type: 'gauge',
- center: ['75%', '50%'], // 默认全局居中
- radius: '30%',
- min: 0,
- max: 2,
- startAngle: 135,
- endAngle: 45,
- splitNumber: 2,
- axisLine: {
- // 坐标轴线
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: [[0.2, 'lime'], [0.8, '#1e90ff'], [1, '#ff4500']],
- width: 2,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- axisTick: {
- // 坐标轴小标记
- length: 12, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: 'auto',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- axisLabel: {
- fontWeight: 'bolder',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- formatter(v) {
- switch (`${v}`) {
- case '0':
- return 'E'
- case '1':
- return 'Gas'
- case '2':
- return 'F'
- default:
- return null
- }
- },
- },
- splitLine: {
- // 分隔线
- length: 15, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle(详见lineStyle)控制线条样式
- width: 3,
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- pointer: {
- width: 2,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- },
- title: {
- show: false,
- },
- detail: {
- show: false,
- },
- data: [{ value: 0.5, name: 'gas' }],
- },
- {
- name: '水表',
- type: 'gauge',
- center: ['75%', '50%'], // 默认全局居中
- radius: '30%',
- min: 0,
- max: 2,
- startAngle: 315,
- endAngle: 225,
- splitNumber: 2,
- axisLine: {
- // 坐标轴线
- lineStyle: {
- // 属性lineStyle控制线条样式
- color: [[0.2, 'lime'], [0.8, '#1e90ff'], [1, '#ff4500']],
- width: 2,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- axisTick: {
- // 坐标轴小标记
- show: false,
- },
- axisLabel: {
- fontWeight: 'bolder',
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- formatter(v) {
- switch (`${v}`) {
- case '0':
- return 'H'
- case '1':
- return 'Water'
- case '2':
- return 'C'
- default:
- return null
- }
- },
- },
- splitLine: {
- // 分隔线
- length: 15, // 属性length控制线长
- lineStyle: {
- // 属性lineStyle(详见lineStyle)控制线条样式
- width: 3,
- color: '#fff',
- shadowColor: '#fff', // 默认透明
- shadowBlur: 10,
- },
- },
- pointer: {
- width: 2,
- shadowColor: '#fff', // 默认透明
- shadowBlur: 5,
- },
- title: {
- show: false,
- },
- detail: {
- show: false,
- },
- data: [{ value: 0.5, name: 'gas' }],
- },
- ],
- }
- this.state = {
- option,
- }
- }
-
- componentDidMount() {
- if (this.timeTicket) {
- clearInterval(this.timeTicket)
- }
- this.timeTicket = setInterval(() => {
- let { option } = this.state
- option.series[0].data[0].value = (Math.random() * 100).toFixed(2) - 0
- option.series[1].data[0].value = (Math.random() * 7).toFixed(2) - 0
- option.series[2].data[0].value = (Math.random() * 2).toFixed(2) - 0
- option.series[3].data[0].value = (Math.random() * 2).toFixed(2) - 0
- this.setState({ option })
- }, 1000)
- }
-
- componentWillUnmount() {
- if (this.timeTicket) {
- clearInterval(this.timeTicket)
- }
- }
-
- render() {
- return (
-
-
- render a car gauge chart.
-
-
-
- )
- }
-}
-
-export default GaugeComponent
diff --git a/src/pages/chart/ECharts/GraphComponent.js b/src/pages/chart/ECharts/GraphComponent.js
deleted file mode 100644
index b9d2498..0000000
--- a/src/pages/chart/ECharts/GraphComponent.js
+++ /dev/null
@@ -1,1363 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-const GraphComponent = () => {
- const getOtion = () => {
- const webkitDep = {
- type: 'force',
- categories: [
- { name: 'HTMLElement', keyword: {}, base: 'HTMLElement' },
- { name: 'WebGL', keyword: {}, base: 'WebGLRenderingContext' },
- { name: 'SVG', keyword: {}, base: 'SVGElement' },
- { name: 'CSS', keyword: {}, base: 'CSSRule' },
- { name: 'Other', keyword: {} },
- ],
- nodes: [
- { name: 'AnalyserNode', value: 1, category: 4 },
- { name: 'AudioNode', value: 1, category: 4 },
- { name: 'Uint8Array', value: 1, category: 4 },
- { name: 'Float32Array', value: 1, category: 4 },
- { name: 'ArrayBuffer', value: 1, category: 4 },
- { name: 'ArrayBufferView', value: 1, category: 4 },
- { name: 'Attr', value: 1, category: 4 },
- { name: 'Node', value: 1, category: 4 },
- { name: 'Element', value: 1, category: 4 },
- { name: 'AudioBuffer', value: 1, category: 4 },
- { name: 'AudioBufferCallback', value: 1, category: 4 },
- { name: 'AudioBufferSourceNode', value: 1, category: 4 },
- { name: 'AudioSourceNode', value: 1, category: 4 },
- { name: 'AudioGain', value: 1, category: 4 },
- { name: 'AudioParam', value: 1, category: 4 },
- { name: 'AudioContext', value: 1, category: 4 },
- { name: 'AudioDestinationNode', value: 1, category: 4 },
- { name: 'AudioListener', value: 1, category: 4 },
- { name: 'BiquadFilterNode', value: 1, category: 4 },
- { name: 'ChannelMergerNode', value: 1, category: 4 },
- { name: 'ChannelSplitterNode', value: 1, category: 4 },
- { name: 'ConvolverNode', value: 1, category: 4 },
- { name: 'DelayNode', value: 1, category: 4 },
- { name: 'DynamicsCompressorNode', value: 1, category: 4 },
- { name: 'GainNode', value: 1, category: 4 },
- { name: 'MediaElementAudioSourceNode', value: 1, category: 4 },
- { name: 'MediaStreamAudioDestinationNode', value: 1, category: 4 },
- { name: 'MediaStreamAudioSourceNode', value: 1, category: 4 },
- { name: 'OscillatorNode', value: 1, category: 4 },
- { name: 'PannerNode', value: 1, category: 4 },
- { name: 'ScriptProcessorNode', value: 1, category: 4 },
- { name: 'WaveShaperNode', value: 1, category: 4 },
- { name: 'WaveTable', value: 1, category: 4 },
- { name: 'CanvasRenderingContext', value: 1, category: 4 },
- { name: 'HTMLCanvasElement', value: 1, category: 0 },
- { name: 'CanvasRenderingContext2D', value: 1, category: 4 },
- { name: 'ImageData', value: 1, category: 4 },
- { name: 'CanvasGradient', value: 1, category: 4 },
- { name: 'CanvasPattern', value: 1, category: 4 },
- { name: 'HTMLImageElement', value: 1, category: 0 },
- { name: 'HTMLVideoElement', value: 1, category: 0 },
- { name: 'TextMetrics', value: 1, category: 4 },
- { name: 'CDATASection', value: 1, category: 4 },
- { name: 'Text', value: 1, category: 4 },
- { name: 'CharacterData', value: 1, category: 4 },
- { name: 'ClientRectList', value: 1, category: 4 },
- { name: 'ClientRect', value: 1, category: 4 },
- { name: 'Clipboard', value: 1, category: 4 },
- { name: 'FileList', value: 1, category: 4 },
- { name: 'DataTransferItemList', value: 1, category: 4 },
- { name: 'Comment', value: 1, category: 4 },
- { name: 'Console', value: 1, category: 4 },
- { name: 'MemoryInfo', value: 1, category: 4 },
- { name: 'Crypto', value: 1, category: 4 },
- { name: 'CSSCharsetRule', value: 1, category: 3 },
- { name: 'CSSRule', value: 3, category: 3 },
- { name: 'CSSFontFaceRule', value: 1, category: 3 },
- { name: 'CSSStyleDeclaration', value: 1, category: 3 },
- { name: 'CSSImportRule', value: 1, category: 3 },
- { name: 'MediaList', value: 1, category: 4 },
- { name: 'CSSStyleSheet', value: 1, category: 3 },
- { name: 'CSSMediaRule', value: 1, category: 3 },
- { name: 'CSSRuleList', value: 1, category: 3 },
- { name: 'CSSPageRule', value: 1, category: 3 },
- { name: 'CSSPrimitiveValue', value: 1, category: 3 },
- { name: 'CSSValue', value: 1, category: 3 },
- { name: 'Counter', value: 1, category: 4 },
- { name: 'RGBColor', value: 1, category: 4 },
- { name: 'Rect', value: 1, category: 4 },
- { name: 'CSSStyleRule', value: 1, category: 3 },
- { name: 'StyleSheet', value: 1, category: 4 },
- { name: 'CSSUnknownRule', value: 1, category: 3 },
- { name: 'CSSValueList', value: 1, category: 3 },
- { name: 'Database', value: 1, category: 4 },
- { name: 'SQLTransactionCallback', value: 1, category: 4 },
- { name: 'DatabaseCallback', value: 1, category: 4 },
- { name: 'DatabaseSync', value: 1, category: 4 },
- { name: 'SQLTransactionSyncCallback', value: 1, category: 4 },
- { name: 'DataTransferItem', value: 1, category: 4 },
- { name: 'StringCallback', value: 1, category: 4 },
- { name: 'Entry', value: 1, category: 4 },
- { name: 'File', value: 1, category: 4 },
- { name: 'DataView', value: 1, category: 4 },
- { name: 'DedicatedWorkerContext', value: 1, category: 4 },
- { name: 'WorkerContext', value: 1, category: 4 },
- { name: 'DirectoryEntry', value: 1, category: 4 },
- { name: 'DirectoryReader', value: 1, category: 4 },
- { name: 'VoidCallback', value: 1, category: 4 },
- { name: 'DirectoryEntrySync', value: 1, category: 4 },
- { name: 'EntrySync', value: 1, category: 4 },
- { name: 'DirectoryReaderSync', value: 1, category: 4 },
- { name: 'FileEntrySync', value: 1, category: 4 },
- { name: 'EntriesCallback', value: 1, category: 4 },
- { name: 'EntryArraySync', value: 1, category: 4 },
- { name: 'DocumentFragment', value: 1, category: 4 },
- { name: 'NodeList', value: 1, category: 4 },
- { name: 'DocumentType', value: 1, category: 4 },
- { name: 'NamedNodeMap', value: 1, category: 4 },
- { name: 'DOMFileSystem', value: 1, category: 4 },
- { name: 'DOMFileSystemSync', value: 1, category: 4 },
- { name: 'DOMImplementation', value: 1, category: 4 },
- { name: 'HTMLDocument', value: 1, category: 0 },
- { name: 'DOMMimeType', value: 1, category: 4 },
- { name: 'DOMPlugin', value: 1, category: 4 },
- { name: 'DOMMimeTypeArray', value: 1, category: 4 },
- { name: 'DOMPluginArray', value: 1, category: 4 },
- { name: 'DOMSelection', value: 1, category: 4 },
- { name: 'Range', value: 1, category: 4 },
- { name: 'DOMSettableTokenList', value: 1, category: 4 },
- { name: 'DOMTokenList', value: 1, category: 4 },
- { name: 'DOMStringMap', value: 1, category: 4 },
- { name: 'ShadowRoot', value: 1, category: 4 },
- { name: 'Entity', value: 1, category: 4 },
- { name: 'EntityReference', value: 1, category: 4 },
- { name: 'EntryArray', value: 1, category: 4 },
- { name: 'MetadataCallback', value: 1, category: 4 },
- { name: 'EntryCallback', value: 1, category: 4 },
- { name: 'Metadata', value: 1, category: 4 },
- { name: 'ErrorCallback', value: 1, category: 4 },
- { name: 'FileError', value: 1, category: 4 },
- { name: 'FileCallback', value: 1, category: 4 },
- { name: 'FileEntry', value: 1, category: 4 },
- { name: 'FileWriterCallback', value: 1, category: 4 },
- { name: 'FileWriterSync', value: 1, category: 4 },
- { name: 'FileReader', value: 1, category: 4 },
- { name: 'FileReaderSync', value: 1, category: 4 },
- { name: 'FileSystemCallback', value: 1, category: 4 },
- { name: 'FileWriter', value: 1, category: 4 },
- { name: 'Float64Array', value: 1, category: 4 },
- { name: 'GamepadList', value: 1, category: 4 },
- { name: 'Gamepad', value: 1, category: 4 },
- { name: 'Geolocation', value: 1, category: 4 },
- { name: 'PositionCallback', value: 1, category: 4 },
- { name: 'Geoposition', value: 1, category: 4 },
- { name: 'Coordinates', value: 1, category: 4 },
- { name: 'HTMLAllCollection', value: 1, category: 0 },
- { name: 'HTMLAnchorElement', value: 1, category: 0 },
- { name: 'HTMLElement', value: 3, category: 0 },
- { name: 'HTMLAppletElement', value: 1, category: 0 },
- { name: 'HTMLAreaElement', value: 1, category: 0 },
- { name: 'HTMLAudioElement', value: 1, category: 0 },
- { name: 'HTMLMediaElement', value: 1, category: 0 },
- { name: 'HTMLBaseElement', value: 1, category: 0 },
- { name: 'HTMLBaseFontElement', value: 1, category: 0 },
- { name: 'HTMLBodyElement', value: 1, category: 0 },
- { name: 'HTMLBRElement', value: 1, category: 0 },
- { name: 'HTMLButtonElement', value: 1, category: 0 },
- { name: 'HTMLFormElement', value: 1, category: 0 },
- { name: 'ValidityState', value: 1, category: 4 },
- { name: 'HTMLCollection', value: 1, category: 0 },
- { name: 'HTMLContentElement', value: 1, category: 0 },
- { name: 'HTMLDataListElement', value: 1, category: 0 },
- { name: 'HTMLDetailsElement', value: 1, category: 0 },
- { name: 'HTMLDirectoryElement', value: 1, category: 0 },
- { name: 'HTMLDivElement', value: 1, category: 0 },
- { name: 'HTMLDListElement', value: 1, category: 0 },
- { name: 'HTMLEmbedElement', value: 1, category: 0 },
- { name: 'SVGDocument', value: 1, category: 2 },
- { name: 'HTMLFieldSetElement', value: 1, category: 0 },
- { name: 'HTMLFontElement', value: 1, category: 0 },
- { name: 'HTMLFormControlsCollection', value: 1, category: 0 },
- { name: 'HTMLFrameElement', value: 1, category: 0 },
- { name: 'HTMLFrameSetElement', value: 1, category: 0 },
- { name: 'HTMLHeadElement', value: 1, category: 0 },
- { name: 'HTMLHeadingElement', value: 1, category: 0 },
- { name: 'HTMLHRElement', value: 1, category: 0 },
- { name: 'HTMLHtmlElement', value: 1, category: 0 },
- { name: 'HTMLIFrameElement', value: 1, category: 0 },
- { name: 'HTMLInputElement', value: 1, category: 0 },
- { name: 'HTMLKeygenElement', value: 1, category: 0 },
- { name: 'HTMLLabelElement', value: 1, category: 0 },
- { name: 'HTMLLegendElement', value: 1, category: 0 },
- { name: 'HTMLLIElement', value: 1, category: 0 },
- { name: 'HTMLLinkElement', value: 1, category: 0 },
- { name: 'HTMLMapElement', value: 1, category: 0 },
- { name: 'HTMLMarqueeElement', value: 1, category: 0 },
- { name: 'TimeRanges', value: 1, category: 4 },
- { name: 'MediaController', value: 1, category: 4 },
- { name: 'MediaError', value: 1, category: 4 },
- { name: 'TextTrackList', value: 1, category: 4 },
- { name: 'TextTrack', value: 1, category: 4 },
- { name: 'HTMLMenuElement', value: 1, category: 0 },
- { name: 'HTMLMetaElement', value: 1, category: 0 },
- { name: 'HTMLMeterElement', value: 1, category: 0 },
- { name: 'HTMLModElement', value: 1, category: 0 },
- { name: 'HTMLObjectElement', value: 1, category: 0 },
- { name: 'HTMLOListElement', value: 1, category: 0 },
- { name: 'HTMLOptGroupElement', value: 1, category: 0 },
- { name: 'HTMLOptionElement', value: 1, category: 0 },
- { name: 'HTMLOptionsCollection', value: 1, category: 0 },
- { name: 'HTMLOutputElement', value: 1, category: 0 },
- { name: 'HTMLParagraphElement', value: 1, category: 0 },
- { name: 'HTMLParamElement', value: 1, category: 0 },
- { name: 'HTMLPreElement', value: 1, category: 0 },
- { name: 'HTMLProgressElement', value: 1, category: 0 },
- { name: 'HTMLQuoteElement', value: 1, category: 0 },
- { name: 'HTMLScriptElement', value: 1, category: 0 },
- { name: 'HTMLSelectElement', value: 1, category: 0 },
- { name: 'HTMLShadowElement', value: 1, category: 0 },
- { name: 'HTMLSourceElement', value: 1, category: 0 },
- { name: 'HTMLSpanElement', value: 1, category: 0 },
- { name: 'HTMLStyleElement', value: 1, category: 0 },
- { name: 'HTMLTableCaptionElement', value: 1, category: 0 },
- { name: 'HTMLTableCellElement', value: 1, category: 0 },
- { name: 'HTMLTableColElement', value: 1, category: 0 },
- { name: 'HTMLTableElement', value: 1, category: 0 },
- { name: 'HTMLTableSectionElement', value: 1, category: 0 },
- { name: 'HTMLTableRowElement', value: 1, category: 0 },
- { name: 'HTMLTextAreaElement', value: 1, category: 0 },
- { name: 'HTMLTitleElement', value: 1, category: 0 },
- { name: 'HTMLTrackElement', value: 1, category: 0 },
- { name: 'HTMLUListElement', value: 1, category: 0 },
- { name: 'HTMLUnknownElement', value: 1, category: 0 },
- { name: 'IDBCursor', value: 1, category: 4 },
- { name: 'IDBAny', value: 1, category: 4 },
- { name: 'IDBKey', value: 1, category: 4 },
- { name: 'IDBRequest', value: 1, category: 4 },
- { name: 'IDBCursorWithValue', value: 1, category: 4 },
- { name: 'IDBDatabase', value: 1, category: 4 },
- { name: 'DOMStringList', value: 1, category: 4 },
- { name: 'IDBObjectStore', value: 1, category: 4 },
- { name: 'IDBTransaction', value: 1, category: 4 },
- { name: 'IDBFactory', value: 1, category: 4 },
- { name: 'IDBVersionChangeRequest', value: 1, category: 4 },
- { name: 'IDBOpenDBRequest', value: 1, category: 4 },
- { name: 'IDBIndex', value: 1, category: 4 },
- { name: 'IDBKeyRange', value: 1, category: 4 },
- { name: 'DOMError', value: 1, category: 4 },
- { name: 'Int16Array', value: 1, category: 4 },
- { name: 'Int32Array', value: 1, category: 4 },
- { name: 'Int8Array', value: 1, category: 4 },
- { name: 'JavaScriptCallFrame', value: 1, category: 4 },
- { name: 'LocalMediaStream', value: 1, category: 4 },
- { name: 'MediaStream', value: 1, category: 4 },
- { name: 'Location', value: 1, category: 4 },
- { name: 'MediaQueryList', value: 1, category: 4 },
- { name: 'MediaQueryListListener', value: 1, category: 4 },
- { name: 'MediaSource', value: 1, category: 4 },
- { name: 'SourceBufferList', value: 1, category: 4 },
- { name: 'SourceBuffer', value: 1, category: 4 },
- { name: 'MediaStreamTrackList', value: 1, category: 4 },
- { name: 'MediaStreamList', value: 1, category: 4 },
- { name: 'MediaStreamTrack', value: 1, category: 4 },
- { name: 'MessageChannel', value: 1, category: 4 },
- { name: 'MessagePort', value: 1, category: 4 },
- { name: 'MutationObserver', value: 1, category: 4 },
- { name: 'MutationRecord', value: 1, category: 4 },
- { name: 'Navigator', value: 1, category: 4 },
- { name: 'BatteryManager', value: 1, category: 4 },
- { name: 'NavigatorUserMediaErrorCallback', value: 1, category: 4 },
- { name: 'NavigatorUserMediaError', value: 1, category: 4 },
- { name: 'NavigatorUserMediaSuccessCallback', value: 1, category: 4 },
- { name: 'NodeFilter', value: 1, category: 4 },
- { name: 'NodeIterator', value: 1, category: 4 },
- { name: 'Notation', value: 1, category: 4 },
- { name: 'Notification', value: 1, category: 4 },
- { name: 'NotificationPermissionCallback', value: 1, category: 4 },
- { name: 'NotificationCenter', value: 1, category: 4 },
- { name: 'OESVertexArrayObject', value: 1, category: 4 },
- { name: 'WebGLVertexArrayObjectOES', value: 1, category: 1 },
- { name: 'Performance', value: 1, category: 4 },
- { name: 'PerformanceNavigation', value: 1, category: 4 },
- { name: 'PerformanceTiming', value: 1, category: 4 },
- { name: 'PositionErrorCallback', value: 1, category: 4 },
- { name: 'PositionError', value: 1, category: 4 },
- { name: 'ProcessingInstruction', value: 1, category: 4 },
- { name: 'RadioNodeList', value: 1, category: 4 },
- { name: 'RTCDataChannel', value: 1, category: 4 },
- { name: 'RTCPeerConnection', value: 1, category: 4 },
- { name: 'RTCSessionDescription', value: 1, category: 4 },
- { name: 'RTCIceCandidate', value: 1, category: 4 },
- { name: 'RTCSessionDescriptionCallback', value: 1, category: 4 },
- { name: 'RTCStatsCallback', value: 1, category: 4 },
- { name: 'RTCStatsResponse', value: 1, category: 4 },
- { name: 'RTCStatsReport', value: 1, category: 4 },
- { name: 'RTCStatsElement', value: 1, category: 4 },
- { name: 'ScriptProfile', value: 1, category: 4 },
- { name: 'ScriptProfileNode', value: 1, category: 4 },
- { name: 'SharedWorker', value: 1, category: 4 },
- { name: 'AbstractWorker', value: 1, category: 4 },
- { name: 'SharedWorkerContext', value: 1, category: 4 },
- { name: 'SpeechGrammarList', value: 1, category: 4 },
- { name: 'SpeechGrammar', value: 1, category: 4 },
- { name: 'SpeechInputResultList', value: 1, category: 4 },
- { name: 'SpeechInputResult', value: 1, category: 4 },
- { name: 'SpeechRecognition', value: 1, category: 4 },
- { name: 'SpeechRecognitionResult', value: 1, category: 4 },
- { name: 'SpeechRecognitionAlternative', value: 1, category: 4 },
- { name: 'SpeechRecognitionResultList', value: 1, category: 4 },
- { name: 'SQLResultSet', value: 1, category: 4 },
- { name: 'SQLResultSetRowList', value: 1, category: 4 },
- { name: 'SQLStatementCallback', value: 1, category: 4 },
- { name: 'SQLTransaction', value: 1, category: 4 },
- { name: 'SQLStatementErrorCallback', value: 1, category: 4 },
- { name: 'SQLTransactionErrorCallback', value: 1, category: 4 },
- { name: 'SQLError', value: 1, category: 4 },
- { name: 'SQLTransactionSync', value: 1, category: 4 },
- { name: 'StorageInfo', value: 1, category: 4 },
- { name: 'StorageInfoUsageCallback', value: 1, category: 4 },
- { name: 'StorageInfoQuotaCallback', value: 1, category: 4 },
- { name: 'StorageInfoErrorCallback', value: 1, category: 4 },
- { name: 'DOMCoreException', value: 1, category: 4 },
- { name: 'StyleSheetList', value: 1, category: 4 },
- { name: 'SVGAElement', value: 1, category: 2 },
- { name: 'SVGTransformable', value: 1, category: 2 },
- { name: 'SVGAnimatedString', value: 1, category: 2 },
- { name: 'SVGAltGlyphDefElement', value: 1, category: 2 },
- { name: 'SVGElement', value: 3, category: 2 },
- { name: 'SVGAltGlyphElement', value: 1, category: 2 },
- { name: 'SVGURIReference', value: 1, category: 2 },
- { name: 'SVGAltGlyphItemElement', value: 1, category: 2 },
- { name: 'SVGAnimateColorElement', value: 1, category: 2 },
- { name: 'SVGAnimationElement', value: 1, category: 2 },
- { name: 'SVGAnimatedAngle', value: 1, category: 2 },
- { name: 'SVGAngle', value: 1, category: 2 },
- { name: 'SVGAnimatedLength', value: 1, category: 2 },
- { name: 'SVGLength', value: 1, category: 2 },
- { name: 'SVGAnimatedLengthList', value: 1, category: 2 },
- { name: 'SVGLengthList', value: 1, category: 2 },
- { name: 'SVGAnimatedNumberList', value: 1, category: 2 },
- { name: 'SVGNumberList', value: 1, category: 2 },
- { name: 'SVGAnimatedPreserveAspectRatio', value: 1, category: 2 },
- { name: 'SVGPreserveAspectRatio', value: 1, category: 2 },
- { name: 'SVGAnimatedRect', value: 1, category: 2 },
- { name: 'SVGRect', value: 1, category: 2 },
- { name: 'SVGAnimatedTransformList', value: 1, category: 2 },
- { name: 'SVGTransformList', value: 1, category: 2 },
- { name: 'SVGAnimateElement', value: 1, category: 2 },
- { name: 'SVGAnimateMotionElement', value: 1, category: 2 },
- { name: 'SVGAnimateTransformElement', value: 1, category: 2 },
- { name: 'ElementTimeControl', value: 1, category: 4 },
- { name: 'SVGCircleElement', value: 1, category: 2 },
- { name: 'SVGClipPathElement', value: 1, category: 2 },
- { name: 'SVGAnimatedEnumeration', value: 1, category: 2 },
- { name: 'SVGColor', value: 1, category: 2 },
- { name: 'SVGComponentTransferFunctionElement', value: 1, category: 2 },
- { name: 'SVGAnimatedNumber', value: 1, category: 2 },
- { name: 'SVGCursorElement', value: 1, category: 2 },
- { name: 'SVGExternalResourcesRequired', value: 1, category: 2 },
- { name: 'SVGDefsElement', value: 1, category: 2 },
- { name: 'SVGDescElement', value: 1, category: 2 },
- { name: 'SVGStylable', value: 1, category: 2 },
- { name: 'SVGSVGElement', value: 1, category: 2 },
- { name: 'SVGElementInstance', value: 1, category: 2 },
- { name: 'EventTarget', value: 1, category: 4 },
- { name: 'SVGElementInstanceList', value: 1, category: 2 },
- { name: 'SVGUseElement', value: 1, category: 2 },
- { name: 'SVGEllipseElement', value: 1, category: 2 },
- { name: 'SVGAnimatedBoolean', value: 1, category: 2 },
- { name: 'SVGFEBlendElement', value: 1, category: 2 },
- { name: 'SVGFilterPrimitiveStandardAttributes', value: 1, category: 2 },
- { name: 'SVGFEColorMatrixElement', value: 1, category: 2 },
- { name: 'SVGFEComponentTransferElement', value: 1, category: 2 },
- { name: 'SVGFECompositeElement', value: 1, category: 2 },
- { name: 'SVGFEConvolveMatrixElement', value: 1, category: 2 },
- { name: 'SVGAnimatedInteger', value: 1, category: 2 },
- { name: 'SVGFEDiffuseLightingElement', value: 1, category: 2 },
- { name: 'SVGFEDisplacementMapElement', value: 1, category: 2 },
- { name: 'SVGFEDistantLightElement', value: 1, category: 2 },
- { name: 'SVGFEDropShadowElement', value: 1, category: 2 },
- { name: 'SVGFEFloodElement', value: 1, category: 2 },
- { name: 'SVGFEFuncAElement', value: 1, category: 2 },
- { name: 'SVGFEFuncBElement', value: 1, category: 2 },
- { name: 'SVGFEFuncGElement', value: 1, category: 2 },
- { name: 'SVGFEFuncRElement', value: 1, category: 2 },
- { name: 'SVGFEGaussianBlurElement', value: 1, category: 2 },
- { name: 'SVGFEImageElement', value: 1, category: 2 },
- { name: 'SVGFEMergeElement', value: 1, category: 2 },
- { name: 'SVGFEMergeNodeElement', value: 1, category: 2 },
- { name: 'SVGFEMorphologyElement', value: 1, category: 2 },
- { name: 'SVGFEOffsetElement', value: 1, category: 2 },
- { name: 'SVGFEPointLightElement', value: 1, category: 2 },
- { name: 'SVGFESpecularLightingElement', value: 1, category: 2 },
- { name: 'SVGFESpotLightElement', value: 1, category: 2 },
- { name: 'SVGFETileElement', value: 1, category: 2 },
- { name: 'SVGFETurbulenceElement', value: 1, category: 2 },
- { name: 'SVGFilterElement', value: 1, category: 2 },
- { name: 'SVGFitToViewBox', value: 1, category: 2 },
- { name: 'SVGFontElement', value: 1, category: 2 },
- { name: 'SVGFontFaceElement', value: 1, category: 2 },
- { name: 'SVGFontFaceFormatElement', value: 1, category: 2 },
- { name: 'SVGFontFaceNameElement', value: 1, category: 2 },
- { name: 'SVGFontFaceSrcElement', value: 1, category: 2 },
- { name: 'SVGFontFaceUriElement', value: 1, category: 2 },
- { name: 'SVGForeignObjectElement', value: 1, category: 2 },
- { name: 'SVGGElement', value: 1, category: 2 },
- { name: 'SVGGlyphElement', value: 1, category: 2 },
- { name: 'SVGGlyphRefElement', value: 1, category: 2 },
- { name: 'SVGGradientElement', value: 1, category: 2 },
- { name: 'SVGHKernElement', value: 1, category: 2 },
- { name: 'SVGImageElement', value: 1, category: 2 },
- { name: 'SVGLinearGradientElement', value: 1, category: 2 },
- { name: 'SVGLineElement', value: 1, category: 2 },
- { name: 'SVGLocatable', value: 1, category: 2 },
- { name: 'SVGMatrix', value: 1, category: 2 },
- { name: 'SVGMarkerElement', value: 1, category: 2 },
- { name: 'SVGMaskElement', value: 1, category: 2 },
- { name: 'SVGMetadataElement', value: 1, category: 2 },
- { name: 'SVGMissingGlyphElement', value: 1, category: 2 },
- { name: 'SVGMPathElement', value: 1, category: 2 },
- { name: 'SVGNumber', value: 1, category: 2 },
- { name: 'SVGPaint', value: 1, category: 2 },
- { name: 'SVGPathElement', value: 1, category: 2 },
- { name: 'SVGPathSegList', value: 1, category: 2 },
- { name: 'SVGPathSegArcAbs', value: 1, category: 2 },
- { name: 'SVGPathSegArcRel', value: 1, category: 2 },
- { name: 'SVGPathSegClosePath', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoCubicAbs', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoCubicRel', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoCubicSmoothAbs', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoCubicSmoothRel', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoQuadraticAbs', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoQuadraticRel', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoQuadraticSmoothAbs', value: 1, category: 2 },
- { name: 'SVGPathSegCurvetoQuadraticSmoothRel', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoAbs', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoHorizontalAbs', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoHorizontalRel', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoRel', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoVerticalAbs', value: 1, category: 2 },
- { name: 'SVGPathSegLinetoVerticalRel', value: 1, category: 2 },
- { name: 'SVGPathSegMovetoAbs', value: 1, category: 2 },
- { name: 'SVGPathSegMovetoRel', value: 1, category: 2 },
- { name: 'SVGPoint', value: 1, category: 2 },
- { name: 'SVGPathSeg', value: 1, category: 2 },
- { name: 'SVGPatternElement', value: 1, category: 2 },
- { name: 'SVGPointList', value: 1, category: 2 },
- { name: 'SVGPolygonElement', value: 1, category: 2 },
- { name: 'SVGPolylineElement', value: 1, category: 2 },
- { name: 'SVGRadialGradientElement', value: 1, category: 2 },
- { name: 'SVGRectElement', value: 1, category: 2 },
- { name: 'SVGScriptElement', value: 1, category: 2 },
- { name: 'SVGSetElement', value: 1, category: 2 },
- { name: 'SVGStopElement', value: 1, category: 2 },
- { name: 'SVGStyleElement', value: 1, category: 2 },
- { name: 'SVGLangSpace', value: 1, category: 2 },
- { name: 'SVGZoomAndPan', value: 1, category: 2 },
- { name: 'SVGViewSpec', value: 1, category: 2 },
- { name: 'SVGTransform', value: 1, category: 2 },
- { name: 'SVGSwitchElement', value: 1, category: 2 },
- { name: 'SVGSymbolElement', value: 1, category: 2 },
- { name: 'SVGTests', value: 1, category: 2 },
- { name: 'SVGStringList', value: 1, category: 2 },
- { name: 'SVGTextContentElement', value: 1, category: 2 },
- { name: 'SVGTextElement', value: 1, category: 2 },
- { name: 'SVGTextPathElement', value: 1, category: 2 },
- { name: 'SVGTextPositioningElement', value: 1, category: 2 },
- { name: 'SVGTitleElement', value: 1, category: 2 },
- { name: 'SVGTRefElement', value: 1, category: 2 },
- { name: 'SVGTSpanElement', value: 1, category: 2 },
- { name: 'SVGViewElement', value: 1, category: 2 },
- { name: 'SVGVKernElement', value: 1, category: 2 },
- { name: 'TextTrackCueList', value: 1, category: 4 },
- { name: 'TextTrackCue', value: 1, category: 4 },
- { name: 'Touch', value: 1, category: 4 },
- { name: 'TouchList', value: 1, category: 4 },
- { name: 'TreeWalker', value: 1, category: 4 },
- { name: 'Uint16Array', value: 1, category: 4 },
- { name: 'Uint32Array', value: 1, category: 4 },
- { name: 'Uint8ClampedArray', value: 1, category: 4 },
- { name: 'WebGLRenderingContext', value: 3, category: 1 },
- { name: 'WebGLProgram', value: 1, category: 1 },
- { name: 'WebGLBuffer', value: 1, category: 1 },
- { name: 'WebGLFramebuffer', value: 1, category: 1 },
- { name: 'WebGLRenderbuffer', value: 1, category: 1 },
- { name: 'WebGLTexture', value: 1, category: 1 },
- { name: 'WebGLShader', value: 1, category: 1 },
- { name: 'WebGLActiveInfo', value: 1, category: 1 },
- { name: 'WebGLContextAttributes', value: 1, category: 1 },
- { name: 'WebGLShaderPrecisionFormat', value: 1, category: 1 },
- { name: 'WebGLUniformLocation', value: 1, category: 1 },
- { name: 'WebKitAnimationList', value: 1, category: 4 },
- { name: 'WebKitAnimation', value: 1, category: 4 },
- { name: 'WebKitCSSFilterValue', value: 1, category: 4 },
- { name: 'WebKitCSSKeyframeRule', value: 1, category: 4 },
- { name: 'WebKitCSSKeyframesRule', value: 1, category: 4 },
- { name: 'WebKitCSSMatrix', value: 1, category: 4 },
- { name: 'WebKitCSSMixFunctionValue', value: 1, category: 4 },
- { name: 'WebKitCSSTransformValue', value: 1, category: 4 },
- { name: 'WebKitNamedFlow', value: 1, category: 4 },
- { name: 'WebSocket', value: 1, category: 4 },
- { name: 'Worker', value: 1, category: 4 },
- { name: 'WorkerLocation', value: 1, category: 4 },
- { name: 'WorkerNavigator', value: 1, category: 4 },
- { name: 'XMLHttpRequest', value: 1, category: 4 },
- { name: 'XMLHttpRequestUpload', value: 1, category: 4 },
- { name: 'DOMFormData', value: 1, category: 4 },
- { name: 'XPathEvaluator', value: 1, category: 4 },
- { name: 'XPathExpression', value: 1, category: 4 },
- { name: 'XPathNSResolver', value: 1, category: 4 },
- { name: 'XPathResult', value: 1, category: 4 },
- { name: 'XSLTProcessor', value: 1, category: 4 },
- ],
- links: [
- { source: 0, target: 1 },
- { source: 0, target: 2 },
- { source: 0, target: 3 },
- { source: 4, target: 4 },
- { source: 5, target: 4 },
- { source: 6, target: 7 },
- { source: 6, target: 8 },
- { source: 9, target: 3 },
- { source: 10, target: 9 },
- { source: 11, target: 12 },
- { source: 11, target: 9 },
- { source: 11, target: 13 },
- { source: 11, target: 14 },
- { source: 15, target: 16 },
- { source: 15, target: 17 },
- { source: 15, target: 0 },
- { source: 15, target: 18 },
- { source: 15, target: 9 },
- { source: 15, target: 11 },
- { source: 15, target: 19 },
- { source: 15, target: 20 },
- { source: 15, target: 21 },
- { source: 15, target: 22 },
- { source: 15, target: 23 },
- { source: 15, target: 24 },
- { source: 15, target: 25 },
- { source: 15, target: 26 },
- { source: 15, target: 27 },
- { source: 15, target: 28 },
- { source: 15, target: 29 },
- { source: 15, target: 30 },
- { source: 15, target: 31 },
- { source: 15, target: 32 },
- { source: 15, target: 4 },
- { source: 16, target: 1 },
- { source: 13, target: 14 },
- { source: 1, target: 15 },
- { source: 1, target: 1 },
- { source: 1, target: 14 },
- { source: 14, target: 3 },
- { source: 12, target: 1 },
- { source: 18, target: 1 },
- { source: 18, target: 14 },
- { source: 18, target: 3 },
- { source: 33, target: 34 },
- { source: 35, target: 33 },
- { source: 35, target: 36 },
- { source: 35, target: 37 },
- { source: 35, target: 38 },
- { source: 35, target: 39 },
- { source: 35, target: 34 },
- { source: 35, target: 40 },
- { source: 35, target: 41 },
- { source: 42, target: 43 },
- { source: 19, target: 1 },
- { source: 20, target: 1 },
- { source: 44, target: 7 },
- { source: 45, target: 46 },
- { source: 47, target: 48 },
- { source: 47, target: 49 },
- { source: 47, target: 39 },
- { source: 50, target: 44 },
- { source: 51, target: 52 },
- { source: 21, target: 1 },
- { source: 21, target: 9 },
- { source: 53, target: 5 },
- { source: 54, target: 55 },
- { source: 56, target: 55 },
- { source: 56, target: 57 },
- { source: 58, target: 55 },
- { source: 58, target: 59 },
- { source: 58, target: 60 },
- { source: 61, target: 55 },
- { source: 61, target: 62 },
- { source: 61, target: 59 },
- { source: 63, target: 55 },
- { source: 63, target: 57 },
- { source: 64, target: 65 },
- { source: 64, target: 66 },
- { source: 64, target: 67 },
- { source: 64, target: 68 },
- { source: 55, target: 55 },
- { source: 55, target: 60 },
- { source: 62, target: 55 },
- { source: 57, target: 55 },
- { source: 57, target: 65 },
- { source: 69, target: 55 },
- { source: 69, target: 57 },
- { source: 60, target: 70 },
- { source: 60, target: 62 },
- { source: 60, target: 55 },
- { source: 71, target: 55 },
- { source: 72, target: 65 },
- { source: 73, target: 74 },
- { source: 75, target: 73 },
- { source: 75, target: 76 },
- { source: 76, target: 77 },
- { source: 78, target: 79 },
- { source: 78, target: 80 },
- { source: 49, target: 81 },
- { source: 49, target: 78 },
- { source: 82, target: 5 },
- { source: 83, target: 84 },
- { source: 22, target: 1 },
- { source: 22, target: 14 },
- { source: 85, target: 80 },
- { source: 85, target: 86 },
- { source: 85, target: 87 },
- { source: 88, target: 89 },
- { source: 88, target: 90 },
- { source: 88, target: 88 },
- { source: 88, target: 91 },
- { source: 86, target: 92 },
- { source: 90, target: 93 },
- { source: 94, target: 7 },
- { source: 94, target: 8 },
- { source: 94, target: 95 },
- { source: 96, target: 7 },
- { source: 96, target: 97 },
- { source: 98, target: 85 },
- { source: 99, target: 88 },
- { source: 100, target: 60 },
- { source: 100, target: 96 },
- { source: 100, target: 101 },
- { source: 102, target: 103 },
- { source: 104, target: 102 },
- { source: 103, target: 102 },
- { source: 105, target: 103 },
- { source: 106, target: 7 },
- { source: 106, target: 107 },
- { source: 108, target: 109 },
- { source: 23, target: 1 },
- { source: 23, target: 14 },
- { source: 8, target: 7 },
- { source: 8, target: 109 },
- { source: 8, target: 110 },
- { source: 8, target: 8 },
- { source: 8, target: 57 },
- { source: 8, target: 6 },
- { source: 8, target: 46 },
- { source: 8, target: 45 },
- { source: 8, target: 95 },
- { source: 8, target: 111 },
- { source: 112, target: 7 },
- { source: 113, target: 7 },
- { source: 92, target: 114 },
- { source: 80, target: 98 },
- { source: 80, target: 85 },
- { source: 80, target: 115 },
- { source: 80, target: 116 },
- { source: 80, target: 87 },
- { source: 114, target: 80 },
- { source: 93, target: 89 },
- { source: 116, target: 80 },
- { source: 89, target: 99 },
- { source: 89, target: 89 },
- { source: 89, target: 117 },
- { source: 89, target: 88 },
- { source: 118, target: 119 },
- { source: 120, target: 81 },
- { source: 121, target: 80 },
- { source: 121, target: 122 },
- { source: 121, target: 120 },
- { source: 91, target: 89 },
- { source: 91, target: 123 },
- { source: 91, target: 81 },
- { source: 48, target: 81 },
- { source: 124, target: 119 },
- { source: 125, target: 4 },
- { source: 126, target: 98 },
- { source: 127, target: 119 },
- { source: 122, target: 127 },
- { source: 3, target: 5 },
- { source: 3, target: 3 },
- { source: 128, target: 5 },
- { source: 128, target: 128 },
- { source: 24, target: 1 },
- { source: 24, target: 13 },
- { source: 129, target: 130 },
- { source: 131, target: 132 },
- { source: 133, target: 134 },
- { source: 135, target: 7 },
- { source: 135, target: 95 },
- { source: 136, target: 137 },
- { source: 138, target: 137 },
- { source: 139, target: 137 },
- { source: 140, target: 141 },
- { source: 142, target: 137 },
- { source: 143, target: 137 },
- { source: 144, target: 137 },
- { source: 145, target: 137 },
- { source: 146, target: 137 },
- { source: 146, target: 147 },
- { source: 146, target: 95 },
- { source: 146, target: 148 },
- { source: 34, target: 137 },
- { source: 149, target: 7 },
- { source: 150, target: 137 },
- { source: 150, target: 95 },
- { source: 151, target: 137 },
- { source: 151, target: 149 },
- { source: 152, target: 137 },
- { source: 153, target: 137 },
- { source: 154, target: 137 },
- { source: 155, target: 137 },
- { source: 101, target: 8 },
- { source: 101, target: 135 },
- { source: 101, target: 149 },
- { source: 137, target: 8 },
- { source: 137, target: 149 },
- { source: 156, target: 137 },
- { source: 156, target: 157 },
- { source: 158, target: 137 },
- { source: 158, target: 149 },
- { source: 158, target: 147 },
- { source: 158, target: 148 },
- { source: 159, target: 137 },
- { source: 160, target: 149 },
- { source: 160, target: 7 },
- { source: 147, target: 137 },
- { source: 147, target: 149 },
- { source: 161, target: 137 },
- { source: 161, target: 157 },
- { source: 162, target: 137 },
- { source: 163, target: 137 },
- { source: 164, target: 137 },
- { source: 165, target: 137 },
- { source: 166, target: 137 },
- { source: 167, target: 137 },
- { source: 167, target: 157 },
- { source: 39, target: 137 },
- { source: 168, target: 137 },
- { source: 168, target: 48 },
- { source: 168, target: 147 },
- { source: 168, target: 95 },
- { source: 168, target: 148 },
- { source: 168, target: 114 },
- { source: 169, target: 137 },
- { source: 169, target: 147 },
- { source: 169, target: 95 },
- { source: 169, target: 148 },
- { source: 170, target: 137 },
- { source: 170, target: 147 },
- { source: 171, target: 137 },
- { source: 171, target: 147 },
- { source: 172, target: 137 },
- { source: 173, target: 137 },
- { source: 173, target: 70 },
- { source: 173, target: 108 },
- { source: 174, target: 137 },
- { source: 174, target: 149 },
- { source: 175, target: 137 },
- { source: 141, target: 137 },
- { source: 141, target: 176 },
- { source: 141, target: 177 },
- { source: 141, target: 178 },
- { source: 141, target: 179 },
- { source: 141, target: 180 },
- { source: 181, target: 137 },
- { source: 182, target: 137 },
- { source: 183, target: 137 },
- { source: 183, target: 95 },
- { source: 184, target: 137 },
- { source: 185, target: 137 },
- { source: 185, target: 147 },
- { source: 185, target: 148 },
- { source: 185, target: 157 },
- { source: 186, target: 137 },
- { source: 187, target: 137 },
- { source: 188, target: 137 },
- { source: 188, target: 147 },
- { source: 189, target: 149 },
- { source: 189, target: 188 },
- { source: 189, target: 7 },
- { source: 190, target: 137 },
- { source: 190, target: 147 },
- { source: 190, target: 108 },
- { source: 190, target: 95 },
- { source: 190, target: 148 },
- { source: 191, target: 137 },
- { source: 192, target: 137 },
- { source: 193, target: 137 },
- { source: 194, target: 137 },
- { source: 194, target: 95 },
- { source: 195, target: 137 },
- { source: 196, target: 137 },
- { source: 197, target: 137 },
- { source: 197, target: 147 },
- { source: 197, target: 95 },
- { source: 197, target: 189 },
- { source: 197, target: 149 },
- { source: 197, target: 148 },
- { source: 197, target: 7 },
- { source: 198, target: 137 },
- { source: 199, target: 137 },
- { source: 200, target: 137 },
- { source: 201, target: 137 },
- { source: 201, target: 70 },
- { source: 202, target: 137 },
- { source: 203, target: 137 },
- { source: 204, target: 137 },
- { source: 205, target: 137 },
- { source: 205, target: 202 },
- { source: 205, target: 149 },
- { source: 205, target: 206 },
- { source: 207, target: 137 },
- { source: 207, target: 149 },
- { source: 206, target: 137 },
- { source: 206, target: 149 },
- { source: 208, target: 137 },
- { source: 208, target: 147 },
- { source: 208, target: 95 },
- { source: 208, target: 148 },
- { source: 209, target: 137 },
- { source: 210, target: 137 },
- { source: 210, target: 180 },
- { source: 211, target: 137 },
- { source: 212, target: 137 },
- { source: 40, target: 141 },
- { source: 213, target: 214 },
- { source: 213, target: 215 },
- { source: 213, target: 216 },
- { source: 217, target: 213 },
- { source: 218, target: 219 },
- { source: 218, target: 214 },
- { source: 218, target: 220 },
- { source: 218, target: 221 },
- { source: 222, target: 215 },
- { source: 222, target: 223 },
- { source: 222, target: 224 },
- { source: 222, target: 216 },
- { source: 225, target: 214 },
- { source: 225, target: 220 },
- { source: 225, target: 216 },
- { source: 226, target: 215 },
- { source: 226, target: 226 },
- { source: 220, target: 219 },
- { source: 220, target: 214 },
- { source: 220, target: 221 },
- { source: 220, target: 216 },
- { source: 220, target: 225 },
- { source: 224, target: 216 },
- { source: 216, target: 227 },
- { source: 216, target: 214 },
- { source: 216, target: 221 },
- { source: 221, target: 218 },
- { source: 221, target: 227 },
- { source: 221, target: 220 },
- { source: 223, target: 216 },
- { source: 228, target: 5 },
- { source: 228, target: 228 },
- { source: 229, target: 5 },
- { source: 229, target: 229 },
- { source: 230, target: 5 },
- { source: 230, target: 230 },
- { source: 231, target: 231 },
- { source: 232, target: 233 },
- { source: 234, target: 219 },
- { source: 177, target: 176 },
- { source: 25, target: 12 },
- { source: 25, target: 141 },
- { source: 235, target: 236 },
- { source: 236, target: 235 },
- { source: 237, target: 238 },
- { source: 237, target: 239 },
- { source: 233, target: 240 },
- { source: 26, target: 12 },
- { source: 26, target: 233 },
- { source: 27, target: 12 },
- { source: 27, target: 233 },
- { source: 241, target: 233 },
- { source: 240, target: 242 },
- { source: 243, target: 244 },
- { source: 115, target: 117 },
- { source: 245, target: 7 },
- { source: 246, target: 95 },
- { source: 246, target: 7 },
- { source: 97, target: 7 },
- { source: 247, target: 131 },
- { source: 247, target: 104 },
- { source: 247, target: 105 },
- { source: 247, target: 248 },
- { source: 247, target: 129 },
- { source: 249, target: 250 },
- { source: 251, target: 232 },
- { source: 7, target: 97 },
- { source: 7, target: 95 },
- { source: 7, target: 7 },
- { source: 7, target: 8 },
- { source: 252, target: 7 },
- { source: 253, target: 252 },
- { source: 253, target: 7 },
- { source: 95, target: 7 },
- { source: 254, target: 7 },
- { source: 255, target: 256 },
- { source: 257, target: 255 },
- { source: 257, target: 87 },
- { source: 258, target: 259 },
- { source: 28, target: 12 },
- { source: 28, target: 14 },
- { source: 28, target: 32 },
- { source: 29, target: 1 },
- { source: 260, target: 52 },
- { source: 260, target: 261 },
- { source: 260, target: 262 },
- { source: 132, target: 133 },
- { source: 263, target: 264 },
- { source: 265, target: 7 },
- { source: 265, target: 70 },
- { source: 266, target: 95 },
- { source: 107, target: 7 },
- { source: 107, target: 94 },
- { source: 107, target: 107 },
- { source: 107, target: 46 },
- { source: 107, target: 45 },
- { source: 68, target: 64 },
- { source: 67, target: 64 },
- { source: 267, target: 4 },
- { source: 267, target: 5 },
- { source: 268, target: 269 },
- { source: 268, target: 241 },
- { source: 268, target: 270 },
- { source: 268, target: 233 },
- { source: 268, target: 271 },
- { source: 268, target: 267 },
- { source: 268, target: 272 },
- { source: 271, target: 269 },
- { source: 272, target: 273 },
- { source: 274, target: 275 },
- { source: 30, target: 1 },
- { source: 276, target: 277 },
- { source: 111, target: 94 },
- { source: 111, target: 8 },
- { source: 111, target: 7 },
- { source: 111, target: 95 },
- { source: 111, target: 106 },
- { source: 278, target: 279 },
- { source: 278, target: 244 },
- { source: 280, target: 84 },
- { source: 239, target: 176 },
- { source: 239, target: 2 },
- { source: 238, target: 239 },
- { source: 281, target: 282 },
- { source: 283, target: 284 },
- { source: 285, target: 281 },
- { source: 286, target: 287 },
- { source: 288, target: 286 },
- { source: 289, target: 290 },
- { source: 291, target: 292 },
- { source: 293, target: 292 },
- { source: 74, target: 292 },
- { source: 294, target: 295 },
- { source: 296, target: 289 },
- { source: 77, target: 296 },
- { source: 297, target: 298 },
- { source: 297, target: 299 },
- { source: 300, target: 301 },
- { source: 70, target: 59 },
- { source: 70, target: 7 },
- { source: 70, target: 70 },
- { source: 302, target: 70 },
- { source: 303, target: 304 },
- { source: 303, target: 305 },
- { source: 306, target: 307 },
- { source: 308, target: 309 },
- { source: 310, target: 307 },
- { source: 311, target: 312 },
- { source: 313, target: 314 },
- { source: 315, target: 316 },
- { source: 317, target: 318 },
- { source: 319, target: 320 },
- { source: 321, target: 322 },
- { source: 323, target: 324 },
- { source: 325, target: 326 },
- { source: 327, target: 312 },
- { source: 328, target: 312 },
- { source: 329, target: 312 },
- { source: 312, target: 330 },
- { source: 312, target: 307 },
- { source: 331, target: 304 },
- { source: 331, target: 315 },
- { source: 332, target: 304 },
- { source: 332, target: 333 },
- { source: 334, target: 65 },
- { source: 334, target: 67 },
- { source: 335, target: 307 },
- { source: 335, target: 336 },
- { source: 335, target: 319 },
- { source: 335, target: 333 },
- { source: 337, target: 338 },
- { source: 337, target: 315 },
- { source: 339, target: 304 },
- { source: 340, target: 341 },
- { source: 157, target: 342 },
- { source: 307, target: 8 },
- { source: 307, target: 342 },
- { source: 307, target: 307 },
- { source: 343, target: 344 },
- { source: 343, target: 345 },
- { source: 343, target: 307 },
- { source: 343, target: 346 },
- { source: 343, target: 343 },
- { source: 345, target: 343 },
- { source: 347, target: 304 },
- { source: 347, target: 315 },
- { source: 338, target: 348 },
- { source: 349, target: 350 },
- { source: 349, target: 305 },
- { source: 349, target: 333 },
- { source: 351, target: 350 },
- { source: 351, target: 305 },
- { source: 351, target: 333 },
- { source: 351, target: 319 },
- { source: 352, target: 350 },
- { source: 352, target: 305 },
- { source: 353, target: 350 },
- { source: 353, target: 305 },
- { source: 353, target: 336 },
- { source: 353, target: 333 },
- { source: 354, target: 350 },
- { source: 354, target: 336 },
- { source: 354, target: 333 },
- { source: 354, target: 305 },
- { source: 354, target: 319 },
- { source: 354, target: 355 },
- { source: 354, target: 348 },
- { source: 356, target: 350 },
- { source: 356, target: 336 },
- { source: 356, target: 305 },
- { source: 357, target: 350 },
- { source: 357, target: 305 },
- { source: 357, target: 336 },
- { source: 357, target: 333 },
- { source: 358, target: 307 },
- { source: 358, target: 336 },
- { source: 359, target: 350 },
- { source: 359, target: 336 },
- { source: 359, target: 305 },
- { source: 360, target: 350 },
- { source: 361, target: 335 },
- { source: 362, target: 335 },
- { source: 363, target: 335 },
- { source: 364, target: 335 },
- { source: 365, target: 350 },
- { source: 365, target: 305 },
- { source: 365, target: 336 },
- { source: 366, target: 350 },
- { source: 366, target: 321 },
- { source: 367, target: 350 },
- { source: 368, target: 307 },
- { source: 368, target: 305 },
- { source: 369, target: 350 },
- { source: 369, target: 305 },
- { source: 369, target: 333 },
- { source: 369, target: 336 },
- { source: 370, target: 350 },
- { source: 370, target: 336 },
- { source: 370, target: 305 },
- { source: 371, target: 307 },
- { source: 371, target: 336 },
- { source: 372, target: 350 },
- { source: 372, target: 305 },
- { source: 372, target: 336 },
- { source: 373, target: 307 },
- { source: 373, target: 336 },
- { source: 374, target: 350 },
- { source: 374, target: 305 },
- { source: 375, target: 350 },
- { source: 375, target: 336 },
- { source: 375, target: 355 },
- { source: 375, target: 333 },
- { source: 376, target: 341 },
- { source: 376, target: 355 },
- { source: 376, target: 333 },
- { source: 376, target: 315 },
- { source: 350, target: 341 },
- { source: 350, target: 315 },
- { source: 350, target: 305 },
- { source: 377, target: 321 },
- { source: 377, target: 323 },
- { source: 378, target: 307 },
- { source: 379, target: 307 },
- { source: 380, target: 307 },
- { source: 381, target: 307 },
- { source: 382, target: 307 },
- { source: 383, target: 307 },
- { source: 384, target: 304 },
- { source: 384, target: 315 },
- { source: 385, target: 304 },
- { source: 386, target: 307 },
- { source: 387, target: 341 },
- { source: 388, target: 341 },
- { source: 388, target: 325 },
- { source: 388, target: 333 },
- { source: 389, target: 307 },
- { source: 390, target: 304 },
- { source: 390, target: 315 },
- { source: 390, target: 321 },
- { source: 318, target: 316 },
- { source: 391, target: 388 },
- { source: 391, target: 315 },
- { source: 392, target: 304 },
- { source: 392, target: 315 },
- { source: 393, target: 307 },
- { source: 393, target: 324 },
- { source: 393, target: 394 },
- { source: 395, target: 377 },
- { source: 395, target: 315 },
- { source: 395, target: 333 },
- { source: 395, target: 313 },
- { source: 395, target: 314 },
- { source: 396, target: 341 },
- { source: 396, target: 315 },
- { source: 396, target: 333 },
- { source: 394, target: 394 },
- { source: 397, target: 307 },
- { source: 398, target: 307 },
- { source: 399, target: 338 },
- { source: 320, target: 400 },
- { source: 401, target: 334 },
- { source: 402, target: 304 },
- { source: 402, target: 403 },
- { source: 402, target: 336 },
- { source: 402, target: 404 },
- { source: 402, target: 405 },
- { source: 402, target: 406 },
- { source: 402, target: 407 },
- { source: 402, target: 408 },
- { source: 402, target: 409 },
- { source: 402, target: 410 },
- { source: 402, target: 411 },
- { source: 402, target: 412 },
- { source: 402, target: 413 },
- { source: 402, target: 414 },
- { source: 402, target: 415 },
- { source: 402, target: 416 },
- { source: 402, target: 417 },
- { source: 402, target: 418 },
- { source: 402, target: 419 },
- { source: 402, target: 420 },
- { source: 402, target: 421 },
- { source: 402, target: 422 },
- { source: 402, target: 423 },
- { source: 404, target: 424 },
- { source: 405, target: 424 },
- { source: 406, target: 424 },
- { source: 407, target: 424 },
- { source: 408, target: 424 },
- { source: 409, target: 424 },
- { source: 410, target: 424 },
- { source: 411, target: 424 },
- { source: 412, target: 424 },
- { source: 413, target: 424 },
- { source: 414, target: 424 },
- { source: 415, target: 424 },
- { source: 416, target: 424 },
- { source: 417, target: 424 },
- { source: 418, target: 424 },
- { source: 419, target: 424 },
- { source: 420, target: 424 },
- { source: 403, target: 424 },
- { source: 421, target: 424 },
- { source: 422, target: 424 },
- { source: 425, target: 377 },
- { source: 425, target: 315 },
- { source: 425, target: 333 },
- { source: 425, target: 325 },
- { source: 423, target: 423 },
- { source: 426, target: 423 },
- { source: 427, target: 304 },
- { source: 427, target: 426 },
- { source: 428, target: 304 },
- { source: 428, target: 426 },
- { source: 429, target: 388 },
- { source: 429, target: 315 },
- { source: 430, target: 304 },
- { source: 430, target: 315 },
- { source: 431, target: 338 },
- { source: 432, target: 312 },
- { source: 433, target: 341 },
- { source: 433, target: 336 },
- { source: 341, target: 305 },
- { source: 341, target: 57 },
- { source: 341, target: 65 },
- { source: 434, target: 435 },
- { source: 342, target: 436 },
- { source: 342, target: 423 },
- { source: 342, target: 437 },
- { source: 342, target: 315 },
- { source: 342, target: 324 },
- { source: 342, target: 307 },
- { source: 342, target: 314 },
- { source: 342, target: 316 },
- { source: 342, target: 394 },
- { source: 342, target: 400 },
- { source: 342, target: 438 },
- { source: 342, target: 8 },
- { source: 342, target: 95 },
- { source: 439, target: 304 },
- { source: 440, target: 377 },
- { source: 441, target: 442 },
- { source: 443, target: 341 },
- { source: 443, target: 333 },
- { source: 443, target: 315 },
- { source: 443, target: 423 },
- { source: 443, target: 324 },
- { source: 444, target: 304 },
- { source: 445, target: 309 },
- { source: 445, target: 333 },
- { source: 445, target: 315 },
- { source: 446, target: 443 },
- { source: 446, target: 317 },
- { source: 446, target: 319 },
- { source: 447, target: 341 },
- { source: 438, target: 394 },
- { source: 304, target: 393 },
- { source: 304, target: 325 },
- { source: 326, target: 438 },
- { source: 448, target: 309 },
- { source: 449, target: 446 },
- { source: 309, target: 305 },
- { source: 346, target: 304 },
- { source: 346, target: 343 },
- { source: 346, target: 315 },
- { source: 450, target: 436 },
- { source: 450, target: 442 },
- { source: 437, target: 321 },
- { source: 437, target: 326 },
- { source: 437, target: 323 },
- { source: 437, target: 307 },
- { source: 451, target: 307 },
- { source: 43, target: 44 },
- { source: 43, target: 43 },
- { source: 180, target: 452 },
- { source: 180, target: 453 },
- { source: 453, target: 180 },
- { source: 453, target: 94 },
- { source: 452, target: 453 },
- { source: 179, target: 180 },
- { source: 454, target: 344 },
- { source: 455, target: 454 },
- { source: 456, target: 7 },
- { source: 456, target: 252 },
- { source: 457, target: 5 },
- { source: 457, target: 457 },
- { source: 458, target: 5 },
- { source: 458, target: 458 },
- { source: 2, target: 5 },
- { source: 2, target: 2 },
- { source: 459, target: 2 },
- { source: 459, target: 459 },
- { source: 31, target: 1 },
- { source: 31, target: 3 },
- { source: 460, target: 33 },
- { source: 460, target: 461 },
- { source: 460, target: 462 },
- { source: 460, target: 463 },
- { source: 460, target: 464 },
- { source: 460, target: 465 },
- { source: 460, target: 4 },
- { source: 460, target: 5 },
- { source: 460, target: 466 },
- { source: 460, target: 467 },
- { source: 460, target: 468 },
- { source: 460, target: 469 },
- { source: 460, target: 470 },
- { source: 460, target: 36 },
- { source: 460, target: 39 },
- { source: 460, target: 34 },
- { source: 460, target: 40 },
- { source: 460, target: 3 },
- { source: 471, target: 472 },
- { source: 473, target: 72 },
- { source: 474, target: 55 },
- { source: 474, target: 57 },
- { source: 475, target: 55 },
- { source: 475, target: 62 },
- { source: 475, target: 474 },
- { source: 476, target: 476 },
- { source: 477, target: 72 },
- { source: 478, target: 72 },
- { source: 479, target: 95 },
- { source: 480, target: 4 },
- { source: 480, target: 5 },
- { source: 481, target: 279 },
- { source: 84, target: 222 },
- { source: 84, target: 482 },
- { source: 84, target: 483 },
- { source: 84, target: 84 },
- { source: 84, target: 257 },
- { source: 84, target: 73 },
- { source: 84, target: 76 },
- { source: 84, target: 126 },
- { source: 84, target: 99 },
- { source: 84, target: 89 },
- { source: 484, target: 485 },
- { source: 484, target: 4 },
- { source: 484, target: 5 },
- { source: 484, target: 486 },
- { source: 487, target: 488 },
- { source: 487, target: 489 },
- { source: 487, target: 490 },
- { source: 488, target: 490 },
- { source: 490, target: 7 },
- { source: 491, target: 7 },
- { source: 491, target: 94 },
- ],
- }
- const option = {
- legend: {
- data: ['HTMLElement', 'WebGL', 'SVG', 'CSS', 'Other'],
- },
- series: [
- {
- type: 'graph',
- layout: 'force',
- animation: false,
- label: {
- position: 'right',
- formatter: '{b}',
- },
- draggable: true,
- data: webkitDep.nodes.map((node, idx) => {
- node.id = idx
- return node
- }),
- categories: webkitDep.categories,
- force: {
- edgeLength: 5,
- repulsion: 20,
- gravity: 0.2,
- },
- edges: webkitDep.links,
- },
- ],
- }
- return option
- }
-
- return (
-
-
- render a graph-webkit-dep.
-
-
-
- )
-}
-
-export default GraphComponent
diff --git a/src/pages/chart/ECharts/LiquidfillComponent.js b/src/pages/chart/ECharts/LiquidfillComponent.js
deleted file mode 100644
index 1fddb3b..0000000
--- a/src/pages/chart/ECharts/LiquidfillComponent.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-import 'echarts-liquidfill'
-
-const LiquidfillComponent = () => {
- const option = {
- series: [
- {
- type: 'liquidFill',
- data: [0.6],
- },
- ],
- }
- return (
-
-
- render a Liquidfill chart:
-
-
-
- )
-}
-
-export default LiquidfillComponent
diff --git a/src/pages/chart/ECharts/LunarCalendarComponent.js b/src/pages/chart/ECharts/LunarCalendarComponent.js
deleted file mode 100644
index 9de4d18..0000000
--- a/src/pages/chart/ECharts/LunarCalendarComponent.js
+++ /dev/null
@@ -1,482 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-
-const LunarCalendarComponent = () => {
- const getOtion = () => {
- let dateList = [
- ['2017-1-1', '初四'],
- ['2017-1-2', '初五'],
- ['2017-1-3', '初六'],
- ['2017-1-4', '初七'],
- ['2017-1-5', '初八', '小寒'],
- ['2017-1-6', '初九'],
- ['2017-1-7', '初十'],
- ['2017-1-8', '十一'],
- ['2017-1-9', '十二'],
- ['2017-1-10', '十三'],
- ['2017-1-11', '十四'],
- ['2017-1-12', '十五'],
- ['2017-1-13', '十六'],
- ['2017-1-14', '十七'],
- ['2017-1-15', '十八'],
- ['2017-1-16', '十九'],
- ['2017-1-17', '二十'],
- ['2017-1-18', '廿一'],
- ['2017-1-19', '廿二'],
- ['2017-1-20', '廿三', '大寒'],
- ['2017-1-21', '廿四'],
- ['2017-1-22', '廿五'],
- ['2017-1-23', '廿六'],
- ['2017-1-24', '廿七'],
- ['2017-1-25', '廿八'],
- ['2017-1-26', '廿九'],
- ['2017-1-27', '三十'],
- ['2017-1-28', '正月'],
- ['2017-1-29', '初二'],
- ['2017-1-30', '初三'],
- ['2017-1-31', '初四'],
- ['2017-2-1', '初五'],
- ['2017-2-2', '初六'],
- ['2017-2-3', '初七', '立春'],
- ['2017-2-4', '初八'],
- ['2017-2-5', '初九'],
- ['2017-2-6', '初十'],
- ['2017-2-7', '十一'],
- ['2017-2-8', '十二'],
- ['2017-2-9', '十三'],
- ['2017-2-10', '十四'],
- ['2017-2-11', '十五'],
- ['2017-2-12', '十六'],
- ['2017-2-13', '十七'],
- ['2017-2-14', '十八'],
- ['2017-2-15', '十九'],
- ['2017-2-16', '二十'],
- ['2017-2-17', '廿一'],
- ['2017-2-18', '廿二', '雨水'],
- ['2017-2-19', '廿三'],
- ['2017-2-20', '廿四'],
- ['2017-2-21', '廿五'],
- ['2017-2-22', '廿六'],
- ['2017-2-23', '廿七'],
- ['2017-2-24', '廿八'],
- ['2017-2-25', '廿九'],
- ['2017-2-26', '二月'],
- ['2017-2-27', '初二'],
- ['2017-2-28', '初三'],
- ['2017-3-1', '初四'],
- ['2017-3-2', '初五'],
- ['2017-3-3', '初六'],
- ['2017-3-4', '初七'],
- ['2017-3-5', '初八', '驚蟄'],
- ['2017-3-6', '初九'],
- ['2017-3-7', '初十'],
- ['2017-3-8', '十一'],
- ['2017-3-9', '十二'],
- ['2017-3-10', '十三'],
- ['2017-3-11', '十四'],
- ['2017-3-12', '十五'],
- ['2017-3-13', '十六'],
- ['2017-3-14', '十七'],
- ['2017-3-15', '十八'],
- ['2017-3-16', '十九'],
- ['2017-3-17', '二十'],
- ['2017-3-18', '廿一'],
- ['2017-3-19', '廿二'],
- ['2017-3-20', '廿三', '春分'],
- ['2017-3-21', '廿四'],
- ['2017-3-22', '廿五'],
- ['2017-3-23', '廿六'],
- ['2017-3-24', '廿七'],
- ['2017-3-25', '廿八'],
- ['2017-3-26', '廿九'],
- ['2017-3-27', '三十'],
- ['2017-3-28', '三月'],
- ['2017-3-29', '初二'],
- ['2017-3-30', '初三'],
- ['2017-3-31', '初四'],
- ['2017-4-1', '初五'],
- ['2017-4-2', '初六'],
- ['2017-4-3', '初七'],
- ['2017-4-4', '初八', '清明'],
- ['2017-4-5', '初九'],
- ['2017-4-6', '初十'],
- ['2017-4-7', '十一'],
- ['2017-4-8', '十二'],
- ['2017-4-9', '十三'],
- ['2017-4-10', '十四'],
- ['2017-4-11', '十五'],
- ['2017-4-12', '十六'],
- ['2017-4-13', '十七'],
- ['2017-4-14', '十八'],
- ['2017-4-15', '十九'],
- ['2017-4-16', '二十'],
- ['2017-4-17', '廿一'],
- ['2017-4-18', '廿二'],
- ['2017-4-19', '廿三'],
- ['2017-4-20', '廿四', '穀雨'],
- ['2017-4-21', '廿五'],
- ['2017-4-22', '廿六'],
- ['2017-4-23', '廿七'],
- ['2017-4-24', '廿八'],
- ['2017-4-25', '廿九'],
- ['2017-4-26', '四月'],
- ['2017-4-27', '初二'],
- ['2017-4-28', '初三'],
- ['2017-4-29', '初四'],
- ['2017-4-30', '初五'],
- ['2017-5-1', '初六'],
- ['2017-5-2', '初七'],
- ['2017-5-3', '初八'],
- ['2017-5-4', '初九'],
- ['2017-5-5', '初十', '立夏'],
- ['2017-5-6', '十一'],
- ['2017-5-7', '十二'],
- ['2017-5-8', '十三'],
- ['2017-5-9', '十四'],
- ['2017-5-10', '十五'],
- ['2017-5-11', '十六'],
- ['2017-5-12', '十七'],
- ['2017-5-13', '十八'],
- ['2017-5-14', '十九'],
- ['2017-5-15', '二十'],
- ['2017-5-16', '廿一'],
- ['2017-5-17', '廿二'],
- ['2017-5-18', '廿三'],
- ['2017-5-19', '廿四'],
- ['2017-5-20', '廿五'],
- ['2017-5-21', '廿六', '小滿'],
- ['2017-5-22', '廿七'],
- ['2017-5-23', '廿八'],
- ['2017-5-24', '廿九'],
- ['2017-5-25', '三十'],
- ['2017-5-26', '五月'],
- ['2017-5-27', '初二'],
- ['2017-5-28', '初三'],
- ['2017-5-29', '初四'],
- ['2017-5-30', '初五'],
- ['2017-5-31', '初六'],
- ['2017-6-1', '初七'],
- ['2017-6-2', '初八'],
- ['2017-6-3', '初九'],
- ['2017-6-4', '初十'],
- ['2017-6-5', '十一', '芒種'],
- ['2017-6-6', '十二'],
- ['2017-6-7', '十三'],
- ['2017-6-8', '十四'],
- ['2017-6-9', '十五'],
- ['2017-6-10', '十六'],
- ['2017-6-11', '十七'],
- ['2017-6-12', '十八'],
- ['2017-6-13', '十九'],
- ['2017-6-14', '二十'],
- ['2017-6-15', '廿一'],
- ['2017-6-16', '廿二'],
- ['2017-6-17', '廿三'],
- ['2017-6-18', '廿四'],
- ['2017-6-19', '廿五'],
- ['2017-6-20', '廿六'],
- ['2017-6-21', '廿七', '夏至'],
- ['2017-6-22', '廿八'],
- ['2017-6-23', '廿九'],
- ['2017-6-24', '六月'],
- ['2017-6-25', '初二'],
- ['2017-6-26', '初三'],
- ['2017-6-27', '初四'],
- ['2017-6-28', '初五'],
- ['2017-6-29', '初六'],
- ['2017-6-30', '初七'],
- ['2017-7-1', '初八'],
- ['2017-7-2', '初九'],
- ['2017-7-3', '初十'],
- ['2017-7-4', '十一'],
- ['2017-7-5', '十二'],
- ['2017-7-6', '十三'],
- ['2017-7-7', '十四', '小暑'],
- ['2017-7-8', '十五'],
- ['2017-7-9', '十六'],
- ['2017-7-10', '十七'],
- ['2017-7-11', '十八'],
- ['2017-7-12', '十九'],
- ['2017-7-13', '二十'],
- ['2017-7-14', '廿一'],
- ['2017-7-15', '廿二'],
- ['2017-7-16', '廿三'],
- ['2017-7-17', '廿四'],
- ['2017-7-18', '廿五'],
- ['2017-7-19', '廿六'],
- ['2017-7-20', '廿七'],
- ['2017-7-21', '廿八'],
- ['2017-7-22', '廿九', '大暑'],
- ['2017-7-23', '閏六'],
- ['2017-7-24', '初二'],
- ['2017-7-25', '初三'],
- ['2017-7-26', '初四'],
- ['2017-7-27', '初五'],
- ['2017-7-28', '初六'],
- ['2017-7-29', '初七'],
- ['2017-7-30', '初八'],
- ['2017-7-31', '初九'],
- ['2017-8-1', '初十'],
- ['2017-8-2', '十一'],
- ['2017-8-3', '十二'],
- ['2017-8-4', '十三'],
- ['2017-8-5', '十四'],
- ['2017-8-6', '十五'],
- ['2017-8-7', '十六', '立秋'],
- ['2017-8-8', '十七'],
- ['2017-8-9', '十八'],
- ['2017-8-10', '十九'],
- ['2017-8-11', '二十'],
- ['2017-8-12', '廿一'],
- ['2017-8-13', '廿二'],
- ['2017-8-14', '廿三'],
- ['2017-8-15', '廿四'],
- ['2017-8-16', '廿五'],
- ['2017-8-17', '廿六'],
- ['2017-8-18', '廿七'],
- ['2017-8-19', '廿八'],
- ['2017-8-20', '廿九'],
- ['2017-8-21', '三十'],
- ['2017-8-22', '七月'],
- ['2017-8-23', '初二', '處暑'],
- ['2017-8-24', '初三'],
- ['2017-8-25', '初四'],
- ['2017-8-26', '初五'],
- ['2017-8-27', '初六'],
- ['2017-8-28', '初七'],
- ['2017-8-29', '初八'],
- ['2017-8-30', '初九'],
- ['2017-8-31', '初十'],
- ['2017-9-1', '十一'],
- ['2017-9-2', '十二'],
- ['2017-9-3', '十三'],
- ['2017-9-4', '十四'],
- ['2017-9-5', '十五'],
- ['2017-9-6', '十六'],
- ['2017-9-7', '十七', '白露'],
- ['2017-9-8', '十八'],
- ['2017-9-9', '十九'],
- ['2017-9-10', '二十'],
- ['2017-9-11', '廿一'],
- ['2017-9-12', '廿二'],
- ['2017-9-13', '廿三'],
- ['2017-9-14', '廿四'],
- ['2017-9-15', '廿五'],
- ['2017-9-16', '廿六'],
- ['2017-9-17', '廿七'],
- ['2017-9-18', '廿八'],
- ['2017-9-19', '廿九'],
- ['2017-9-20', '八月'],
- ['2017-9-21', '初二'],
- ['2017-9-22', '初三'],
- ['2017-9-23', '初四', '秋分'],
- ['2017-9-24', '初五'],
- ['2017-9-25', '初六'],
- ['2017-9-26', '初七'],
- ['2017-9-27', '初八'],
- ['2017-9-28', '初九'],
- ['2017-9-29', '初十'],
- ['2017-9-30', '十一'],
- ['2017-10-1', '十二'],
- ['2017-10-2', '十三'],
- ['2017-10-3', '十四'],
- ['2017-10-4', '十五'],
- ['2017-10-5', '十六'],
- ['2017-10-6', '十七'],
- ['2017-10-7', '十八'],
- ['2017-10-8', '十九', '寒露'],
- ['2017-10-9', '二十'],
- ['2017-10-10', '廿一'],
- ['2017-10-11', '廿二'],
- ['2017-10-12', '廿三'],
- ['2017-10-13', '廿四'],
- ['2017-10-14', '廿五'],
- ['2017-10-15', '廿六'],
- ['2017-10-16', '廿七'],
- ['2017-10-17', '廿八'],
- ['2017-10-18', '廿九'],
- ['2017-10-19', '三十'],
- ['2017-10-20', '九月'],
- ['2017-10-21', '初二'],
- ['2017-10-22', '初三'],
- ['2017-10-23', '初四', '霜降'],
- ['2017-10-24', '初五'],
- ['2017-10-25', '初六'],
- ['2017-10-26', '初七'],
- ['2017-10-27', '初八'],
- ['2017-10-28', '初九'],
- ['2017-10-29', '初十'],
- ['2017-10-30', '十一'],
- ['2017-10-31', '十二'],
- ['2017-11-1', '十三'],
- ['2017-11-2', '十四'],
- ['2017-11-3', '十五'],
- ['2017-11-4', '十六'],
- ['2017-11-5', '十七'],
- ['2017-11-6', '十八'],
- ['2017-11-7', '十九', '立冬'],
- ['2017-11-8', '二十'],
- ['2017-11-9', '廿一'],
- ['2017-11-10', '廿二'],
- ['2017-11-11', '廿三'],
- ['2017-11-12', '廿四'],
- ['2017-11-13', '廿五'],
- ['2017-11-14', '廿六'],
- ['2017-11-15', '廿七'],
- ['2017-11-16', '廿八'],
- ['2017-11-17', '廿九'],
- ['2017-11-18', '十月'],
- ['2017-11-19', '初二'],
- ['2017-11-20', '初三'],
- ['2017-11-21', '初四'],
- ['2017-11-22', '初五', '小雪'],
- ['2017-11-23', '初六'],
- ['2017-11-24', '初七'],
- ['2017-11-25', '初八'],
- ['2017-11-26', '初九'],
- ['2017-11-27', '初十'],
- ['2017-11-28', '十一'],
- ['2017-11-29', '十二'],
- ['2017-11-30', '十三'],
- ['2017-12-1', '十四'],
- ['2017-12-2', '十五'],
- ['2017-12-3', '十六'],
- ['2017-12-4', '十七'],
- ['2017-12-5', '十八'],
- ['2017-12-6', '十九'],
- ['2017-12-7', '二十', '大雪'],
- ['2017-12-8', '廿一'],
- ['2017-12-9', '廿二'],
- ['2017-12-10', '廿三'],
- ['2017-12-11', '廿四'],
- ['2017-12-12', '廿五'],
- ['2017-12-13', '廿六'],
- ['2017-12-14', '廿七'],
- ['2017-12-15', '廿八'],
- ['2017-12-16', '廿九'],
- ['2017-12-17', '三十'],
- ['2017-12-18', '十一月'],
- ['2017-12-19', '初二'],
- ['2017-12-20', '初三'],
- ['2017-12-21', '初四'],
- ['2017-12-22', '初五', '冬至'],
- ['2017-12-23', '初六'],
- ['2017-12-24', '初七'],
- ['2017-12-25', '初八'],
- ['2017-12-26', '初九'],
- ['2017-12-27', '初十'],
- ['2017-12-28', '十一'],
- ['2017-12-29', '十二'],
- ['2017-12-30', '十三'],
- ['2017-12-31', '十四'],
- ]
-
- let heatmapData = []
- let lunarData = []
- for (let i = 0; i < dateList.length; i++) {
- heatmapData.push([dateList[i][0], Math.random() * 300])
- lunarData.push([dateList[i][0], 1, dateList[i][1], dateList[i][2]])
- }
-
- const option = {
- tooltip: {
- formatter(params) {
- return `降雨量: ${params.value[1].toFixed(2)}`
- },
- },
-
- visualMap: {
- show: false,
- min: 0,
- max: 300,
- calculable: true,
- seriesIndex: [2],
- orient: 'horizontal',
- left: 'center',
- bottom: 20,
- inRange: {
- color: ['#e0ffff', '#006edd'],
- opacity: 0.3,
- },
- controller: {
- inRange: {
- opacity: 0.5,
- },
- },
- },
-
- calendar: [
- {
- left: 'center',
- top: 'middle',
- cellSize: [70, 70],
- yearLabel: { show: false },
- orient: 'vertical',
- dayLabel: {
- firstDay: 1,
- nameMap: 'cn',
- },
- monthLabel: {
- show: false,
- },
- range: '2017-03',
- },
- ],
-
- series: [
- {
- type: 'scatter',
- coordinateSystem: 'calendar',
- symbolSize: 1,
- label: {
- show: true,
- formatter(params) {
- let d = echarts.number.parseDate(params.value[0])
- return `${d.getDate()}\n\n${params.value[2]}\n\n`
- },
- color: '#000',
- },
- data: lunarData,
- },
- {
- type: 'scatter',
- coordinateSystem: 'calendar',
- symbolSize: 1,
- label: {
- show: true,
- formatter(params) {
- return `\n\n\n${params.value[3] || ''}`
- },
- fontSize: 14,
- fontWeight: 700,
- color: '#a00',
- },
- data: lunarData,
- },
- {
- name: '降雨量',
- type: 'heatmap',
- coordinateSystem: 'calendar',
- data: heatmapData,
- },
- ],
- }
- return option
- }
-
- return (
-
-
- render a lunar calendar chart.
-
-
-
- )
-}
-
-export default LunarCalendarComponent
diff --git a/src/pages/chart/ECharts/MainPageComponent.js b/src/pages/chart/ECharts/MainPageComponent.js
deleted file mode 100644
index ed0a5cd..0000000
--- a/src/pages/chart/ECharts/MainPageComponent.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react'
-import AdSense from 'react-adsense'
-import { Link } from 'umi'
-import DynamicChartComponent from './DynamicChartComponent.js'
-
-const MainPageComponent = () => {
- return (
-
-
echarts-for-react {this.props.params.type}
-
- {' '}
- A very simple echarts(v3.0) wrapper for React.{' '}
-
- hustcc/echarts-for-react
-
-
-
-
-
-
- Simple demo |
- Echarts loading |
- Echarts API |
- Echarts events |
- Echarts theme |
- Dynamic chart |
- Map chart
-
-
- New
- :
- Airport |
- Graph |
- Calendar |
- Treemap |
- Gauge |
- GCalendar |
- Lunar |
- Liquidfill
-
- {this.props.children ||
}
-
-
-
- )
-}
-
-export default MainPageComponent
diff --git a/src/pages/chart/ECharts/ModuleLoadChartComponent.js b/src/pages/chart/ECharts/ModuleLoadChartComponent.js
deleted file mode 100644
index 852fcdb..0000000
--- a/src/pages/chart/ECharts/ModuleLoadChartComponent.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-const ModuleLoadChartComponent = () => {
- const option = {
- title: { text: 'ECharts 入门示例' },
- tooltip: {},
- xAxis: {
- data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
- },
- yAxis: {},
- series: [
- {
- name: '销量',
- type: 'bar',
- data: [5, 20, 36, 10, 10, 20],
- },
- ],
- }
-
- let code =
- ' "
- return (
-
-
-
- {' '}
- load echarts module as you wish
- reduce the file size
- :{' '}
-
-
-
code below:
-
- {code}
-
-
-
- )
-}
-
-export default ModuleLoadChartComponent
diff --git a/src/pages/chart/ECharts/SimpleChartComponent.js b/src/pages/chart/ECharts/SimpleChartComponent.js
deleted file mode 100644
index c1c3922..0000000
--- a/src/pages/chart/ECharts/SimpleChartComponent.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import './theme/macarons.js'
-
-const SimpleChartComponent = () => {
- const option = {
- title: {
- text: '堆叠区域图',
- },
- tooltip: {
- trigger: 'axis',
- },
- legend: {
- data: ['邮件营销', '联盟广告', '视频广告'],
- },
- toolbox: {
- feature: {
- saveAsImage: {},
- },
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true,
- },
- xAxis: [
- {
- type: 'category',
- boundaryGap: false,
- data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
- },
- ],
- yAxis: [
- {
- type: 'value',
- },
- ],
- series: [
- {
- name: '邮件营销',
- type: 'line',
- stack: '总量',
- areaStyle: {},
- data: [120, 132, 101, 134, 90, 230, 210],
- },
- {
- name: '联盟广告',
- type: 'line',
- stack: '总量',
- areaStyle: {},
- data: [220, 182, 191, 234, 290, 330, 310],
- },
- {
- name: '视频广告',
- type: 'line',
- stack: '总量',
- areaStyle: {},
- data: [150, 232, 201, 154, 190, 330, 410],
- },
- ],
- }
- let code =
- ' "
- return (
-
-
-
- {' '}
- render a Simple echart With option and height :{' '}
-
-
-
code below:
-
- {code}
-
-
-
- )
-}
-
-export default SimpleChartComponent
diff --git a/src/pages/chart/ECharts/ThemeChartComponent.js b/src/pages/chart/ECharts/ThemeChartComponent.js
deleted file mode 100644
index 06332f0..0000000
--- a/src/pages/chart/ECharts/ThemeChartComponent.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-
-import * as echarts from 'echarts'
-
-const ThemeChartComponent = () => {
- const option = {
- title: {
- text: '阶梯瀑布图',
- subtext: 'From ExcelHome',
- sublink: 'http://e.weibo.com/1341556070/Aj1J2x5a5',
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- // 坐标轴指示器,坐标轴触发有效
- type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
- },
- },
- legend: {
- data: ['支出', '收入'],
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true,
- },
- xAxis: {
- type: 'category',
- splitLine: { show: false },
- data: [
- '11月1日',
- '11月2日',
- '11月3日',
- '11月4日',
- '11月5日',
- '11月6日',
- '11月7日',
- '11月8日',
- '11月9日',
- '11月10日',
- '11月11日',
- ],
- },
- yAxis: {
- type: 'value',
- },
- series: [
- {
- name: '辅助',
- type: 'bar',
- stack: '总量',
- itemStyle: {
- borderColor: 'rgba(0,0,0,0)',
- color: 'rgba(0,0,0,0)',
- },
- emphasis: {
- itemStyle: {
- borderColor: 'rgba(0,0,0,0)',
- color: 'rgba(0,0,0,0)',
- },
- },
- data: [0, 900, 1245, 1530, 1376, 1376, 1511, 1689, 1856, 1495, 1292],
- },
- {
- name: '收入',
- type: 'bar',
- stack: '总量',
- label: {
- show: true,
- position: 'top',
- },
- data: [900, 345, 393, '-', '-', 135, 178, 286, '-', '-', '-'],
- },
- {
- name: '支出',
- type: 'bar',
- stack: '总量',
- label: {
- show: true,
- position: 'bottom',
- },
- data: ['-', '-', '-', 108, 154, '-', '-', '-', 119, 361, 203],
- },
- ],
- }
-
- echarts.registerTheme('my_theme', {
- backgroundColor: '#f4cccc',
- })
-
- let code =
- "echarts.registerTheme('my_theme', {\n" +
- " backgroundColor: '#f4cccc'\n" +
- '});\n\n' +
- ' "
- return (
-
-
-
- {' '}
- render a echart With theme , should{' '}
- echarts.registerTheme(themeName, themeObj) before
- use.
-
-
-
- {' '}
- the theme object format:
- https://github.com/ecomfe/echarts/blob/master/theme/dark.js
-
-
- {code}
-
-
-
- )
-}
-
-export default ThemeChartComponent
diff --git a/src/pages/chart/ECharts/TransparentBar3DComPonent.js b/src/pages/chart/ECharts/TransparentBar3DComPonent.js
deleted file mode 100644
index db0400b..0000000
--- a/src/pages/chart/ECharts/TransparentBar3DComPonent.js
+++ /dev/null
@@ -1,298 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import 'echarts-gl'
-
-const hours = [
- '12a',
- '1a',
- '2a',
- '3a',
- '4a',
- '5a',
- '6a',
- '7a',
- '8a',
- '9a',
- '10a',
- '11a',
- '12p',
- '1p',
- '2p',
- '3p',
- '4p',
- '5p',
- '6p',
- '7p',
- '8p',
- '9p',
- '10p',
- '11p',
-]
-const days = [
- 'Saturday',
- 'Friday',
- 'Thursday',
- 'Wednesday',
- 'Tuesday',
- 'Monday',
- 'Sunday',
-]
-
-const data = [
- [0, 0, 5],
- [0, 1, 1],
- [0, 2, 0],
- [0, 3, 0],
- [0, 4, 0],
- [0, 5, 0],
- [0, 6, 0],
- [0, 7, 0],
- [0, 8, 0],
- [0, 9, 0],
- [0, 10, 0],
- [0, 11, 2],
- [0, 12, 4],
- [0, 13, 1],
- [0, 14, 1],
- [0, 15, 3],
- [0, 16, 4],
- [0, 17, 6],
- [0, 18, 4],
- [0, 19, 4],
- [0, 20, 3],
- [0, 21, 3],
- [0, 22, 2],
- [0, 23, 5],
- [1, 0, 7],
- [1, 1, 0],
- [1, 2, 0],
- [1, 3, 0],
- [1, 4, 0],
- [1, 5, 0],
- [1, 6, 0],
- [1, 7, 0],
- [1, 8, 0],
- [1, 9, 0],
- [1, 10, 5],
- [1, 11, 2],
- [1, 12, 2],
- [1, 13, 6],
- [1, 14, 9],
- [1, 15, 11],
- [1, 16, 6],
- [1, 17, 7],
- [1, 18, 8],
- [1, 19, 12],
- [1, 20, 5],
- [1, 21, 5],
- [1, 22, 7],
- [1, 23, 2],
- [2, 0, 1],
- [2, 1, 1],
- [2, 2, 0],
- [2, 3, 0],
- [2, 4, 0],
- [2, 5, 0],
- [2, 6, 0],
- [2, 7, 0],
- [2, 8, 0],
- [2, 9, 0],
- [2, 10, 3],
- [2, 11, 2],
- [2, 12, 1],
- [2, 13, 9],
- [2, 14, 8],
- [2, 15, 10],
- [2, 16, 6],
- [2, 17, 5],
- [2, 18, 5],
- [2, 19, 5],
- [2, 20, 7],
- [2, 21, 4],
- [2, 22, 2],
- [2, 23, 4],
- [3, 0, 7],
- [3, 1, 3],
- [3, 2, 0],
- [3, 3, 0],
- [3, 4, 0],
- [3, 5, 0],
- [3, 6, 0],
- [3, 7, 0],
- [3, 8, 1],
- [3, 9, 0],
- [3, 10, 5],
- [3, 11, 4],
- [3, 12, 7],
- [3, 13, 14],
- [3, 14, 13],
- [3, 15, 12],
- [3, 16, 9],
- [3, 17, 5],
- [3, 18, 5],
- [3, 19, 10],
- [3, 20, 6],
- [3, 21, 4],
- [3, 22, 4],
- [3, 23, 1],
- [4, 0, 1],
- [4, 1, 3],
- [4, 2, 0],
- [4, 3, 0],
- [4, 4, 0],
- [4, 5, 1],
- [4, 6, 0],
- [4, 7, 0],
- [4, 8, 0],
- [4, 9, 2],
- [4, 10, 4],
- [4, 11, 4],
- [4, 12, 2],
- [4, 13, 4],
- [4, 14, 4],
- [4, 15, 14],
- [4, 16, 12],
- [4, 17, 1],
- [4, 18, 8],
- [4, 19, 5],
- [4, 20, 3],
- [4, 21, 7],
- [4, 22, 3],
- [4, 23, 0],
- [5, 0, 2],
- [5, 1, 1],
- [5, 2, 0],
- [5, 3, 3],
- [5, 4, 0],
- [5, 5, 0],
- [5, 6, 0],
- [5, 7, 0],
- [5, 8, 2],
- [5, 9, 0],
- [5, 10, 4],
- [5, 11, 1],
- [5, 12, 5],
- [5, 13, 10],
- [5, 14, 5],
- [5, 15, 7],
- [5, 16, 11],
- [5, 17, 6],
- [5, 18, 0],
- [5, 19, 5],
- [5, 20, 3],
- [5, 21, 4],
- [5, 22, 2],
- [5, 23, 0],
- [6, 0, 1],
- [6, 1, 0],
- [6, 2, 0],
- [6, 3, 0],
- [6, 4, 0],
- [6, 5, 0],
- [6, 6, 0],
- [6, 7, 0],
- [6, 8, 0],
- [6, 9, 0],
- [6, 10, 1],
- [6, 11, 0],
- [6, 12, 2],
- [6, 13, 1],
- [6, 14, 3],
- [6, 15, 4],
- [6, 16, 0],
- [6, 17, 0],
- [6, 18, 0],
- [6, 19, 0],
- [6, 20, 1],
- [6, 21, 2],
- [6, 22, 2],
- [6, 23, 6],
-]
-
-const option = {
- tooltip: {},
- visualMap: {
- max: 20,
- inRange: {
- color: [
- '#313695',
- '#4575b4',
- '#74add1',
- '#abd9e9',
- '#e0f3f8',
- '#ffffbf',
- '#fee090',
- '#fdae61',
- '#f46d43',
- '#d73027',
- '#a50026',
- ],
- },
- },
- xAxis3D: {
- type: 'category',
- data: hours,
- },
- yAxis3D: {
- type: 'category',
- data: days,
- },
- zAxis3D: {
- type: 'value',
- },
- grid3D: {
- boxWidth: 200,
- boxDepth: 80,
- light: {
- main: {
- intensity: 1.2,
- },
- ambient: {
- intensity: 0.3,
- },
- },
- },
- series: [
- {
- type: 'bar3D',
- data: data.map(item => {
- return {
- value: [item[1], item[0], item[2]],
- }
- }),
- shading: 'color',
-
- label: {
- show: false,
- fontSize: 16,
- borderWidth: 1,
- },
-
- itemStyle: {
- opacity: 0.4,
- },
-
- emphasis: {
- label: {
- fontSize: 20,
- color: '#900',
- },
- itemStyle: {
- color: '#900',
- },
- },
- },
- ],
-}
-
-const TransparentBar3DComPonent = () => {
- return (
-
- )
-}
-
-export default TransparentBar3DComPonent
diff --git a/src/pages/chart/ECharts/TreemapComponent.js b/src/pages/chart/ECharts/TreemapComponent.js
deleted file mode 100644
index 8c46cd2..0000000
--- a/src/pages/chart/ECharts/TreemapComponent.js
+++ /dev/null
@@ -1,267 +0,0 @@
-import React from 'react'
-import ReactEcharts from 'echarts-for-react'
-import * as echarts from 'echarts'
-
-const TreemapComponent = () => {
- let diskData = [
- {
- value: 180,
- name: 'Accounts',
- path: 'Accounts',
- children: [
- {
- value: 76,
- name: 'Access',
- path: 'Accounts/Access',
- children: [
- {
- value: 12,
- name: 'DefaultAccessPlugin.bundle',
- path: 'Accounts/Access/DefaultAccessPlugin.bundle',
- },
- {
- value: 28,
- name: 'FacebookAccessPlugin.bundle',
- path: 'Accounts/Access/FacebookAccessPlugin.bundle',
- },
- {
- value: 20,
- name: 'LinkedInAccessPlugin.bundle',
- path: 'Accounts/Access/LinkedInAccessPlugin.bundle',
- },
- {
- value: 16,
- name: 'TencentWeiboAccessPlugin.bundle',
- path: 'Accounts/Access/TencentWeiboAccessPlugin.bundle',
- },
- ],
- },
- {
- value: 92,
- name: 'Authentication',
- path: 'Accounts/Authentication',
- children: [
- {
- value: 24,
- name: 'FacebookAuthenticationPlugin.bundle',
- path:
- 'Accounts/Authentication/FacebookAuthenticationPlugin.bundle',
- },
- {
- value: 16,
- name: 'LinkedInAuthenticationPlugin.bundle',
- path:
- 'Accounts/Authentication/LinkedInAuthenticationPlugin.bundle',
- },
- {
- value: 20,
- name: 'TencentWeiboAuthenticationPlugin.bundle',
- path:
- 'Accounts/Authentication/TencentWeiboAuthenticationPlugin.bundle',
- },
- {
- value: 16,
- name: 'TwitterAuthenticationPlugin.bundle',
- path:
- 'Accounts/Authentication/TwitterAuthenticationPlugin.bundle',
- },
- {
- value: 16,
- name: 'WeiboAuthenticationPlugin.bundle',
- path: 'Accounts/Authentication/WeiboAuthenticationPlugin.bundle',
- },
- ],
- },
- {
- value: 12,
- name: 'Notification',
- path: 'Accounts/Notification',
- children: [
- {
- value: 12,
- name: 'SPAAccountsNotificationPlugin.bundle',
- path:
- 'Accounts/Notification/SPAAccountsNotificationPlugin.bundle',
- },
- ],
- },
- ],
- },
- {
- value: 1904,
- name: 'AddressBook Plug-Ins',
- path: 'AddressBook Plug-Ins',
- children: [
- {
- value: 744,
- name: 'CardDAVPlugin.sourcebundle',
- path: 'AddressBook Plug-Ins/CardDAVPlugin.sourcebundle',
- children: [
- {
- value: 744,
- name: 'Contents',
- path: 'AddressBook Plug-Ins/CardDAVPlugin.sourcebundle/Contents',
- },
- ],
- },
- {
- value: 28,
- name: 'DirectoryServices.sourcebundle',
- path: 'AddressBook Plug-Ins/DirectoryServices.sourcebundle',
- children: [
- {
- value: 28,
- name: 'Contents',
- path:
- 'AddressBook Plug-Ins/DirectoryServices.sourcebundle/Contents',
- },
- ],
- },
- {
- value: 680,
- name: 'Exchange.sourcebundle',
- path: 'AddressBook Plug-Ins/Exchange.sourcebundle',
- children: [
- {
- value: 680,
- name: 'Contents',
- path: 'AddressBook Plug-Ins/Exchange.sourcebundle/Contents',
- },
- ],
- },
- {
- value: 432,
- name: 'LDAP.sourcebundle',
- path: 'AddressBook Plug-Ins/LDAP.sourcebundle',
- children: [
- {
- value: 432,
- name: 'Contents',
- path: 'AddressBook Plug-Ins/LDAP.sourcebundle/Contents',
- },
- ],
- },
- {
- value: 20,
- name: 'LocalSource.sourcebundle',
- path: 'AddressBook Plug-Ins/LocalSource.sourcebundle',
- children: [
- {
- value: 20,
- name: 'Contents',
- path: 'AddressBook Plug-Ins/LocalSource.sourcebundle/Contents',
- },
- ],
- },
- ],
- },
- {
- value: 36,
- name: 'Assistant',
- path: 'Assistant',
- children: [
- {
- value: 36,
- name: 'Plugins',
- path: 'Assistant/Plugins',
- children: [
- {
- value: 36,
- name: 'AddressBook.assistantBundle',
- path: 'Assistant/Plugins/AddressBook.assistantBundle',
- },
- {
- value: 8,
- name: 'GenericAddressHandler.addresshandler',
- path: 'Recents/Plugins/GenericAddressHandler.addresshandler',
- },
- {
- value: 12,
- name: 'MapsRecents.addresshandler',
- path: 'Recents/Plugins/MapsRecents.addresshandler',
- },
- ],
- },
- ],
- },
- ]
- let formatUtil = echarts.format
- function getLevelOption() {
- return [
- {
- itemStyle: {
- borderWidth: 0,
- gapWidth: 5,
- },
- },
- {
- itemStyle: {
- gapWidth: 1,
- },
- },
- {
- colorSaturation: [0.35, 0.5],
- itemStyle: {
- gapWidth: 1,
- borderColorSaturation: 0.6,
- },
- },
- ]
- }
- const option = {
- title: {
- text: 'Disk Usage',
- left: 'center',
- },
-
- tooltip: {
- formatter(info) {
- let { value, treePathInfo } = info
- let treePath = []
-
- for (let i = 1; i < treePathInfo.length; i++) {
- treePath.push(treePathInfo[i].name)
- }
-
- return [
- `${formatUtil.encodeHTML(
- treePath.join('/')
- )}
`,
- `Disk Usage: ${formatUtil.addCommas(value)} KB`,
- ].join('')
- },
- },
-
- series: [
- {
- name: 'Disk Usage',
- type: 'treemap',
- visibleMin: 300,
- label: {
- show: true,
- formatter: '{b}',
- },
- itemStyle: {
- borderColor: '#fff',
- },
- levels: getLevelOption(),
- data: diskData,
- },
- ],
- }
-
- return (
-
-
- render a disk usage treemap.
-
-
-
- )
-}
-
-export default TreemapComponent
diff --git a/src/pages/chart/ECharts/index.js b/src/pages/chart/ECharts/index.js
deleted file mode 100644
index cf28993..0000000
--- a/src/pages/chart/ECharts/index.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React from 'react'
-import { Radio } from 'antd'
-import { Page } from 'components'
-import EchartsComponent from './EchartsComponent'
-import styles from './index.less'
-
-const RadioGroup = Radio.Group
-
-const chartList = [
- {
- label: 'SimpleChart',
- value: 'simple',
- },
- {
- label: 'ChartShowLoading',
- value: 'loading',
- },
- {
- label: 'ChartAPI',
- value: 'api',
- },
- {
- label: 'ChartWithEvent',
- value: 'events',
- },
- {
- label: 'ThemeChart',
- value: 'theme',
- },
- {
- label: 'DynamicChart',
- value: 'dynamic',
- },
- {
- label: 'Graph',
- value: 'graph',
- },
- {
- label: 'Calendar',
- value: 'calendar',
- },
- {
- label: 'Treemap',
- value: 'treemap',
- },
- {
- label: 'Gauge',
- value: 'gauge',
- },
- {
- label: 'GCalendar',
- value: 'gcalendar',
- },
- {
- label: 'LunarCalendar',
- value: 'lunar',
- },
- {
- label: 'Liquidfill',
- value: 'liquid',
- },
- {
- label: 'BubbleGradient',
- value: 'BubbleGradientComponent',
- },
- {
- label: 'TransparentBar3D',
- value: 'TransparentBar3DComPonent',
- },
-]
-
-class Chart extends React.Component {
- constructor() {
- super()
- this.state = {
- type: '',
- }
- this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
- }
- handleRadioGroupChange(e) {
- this.setState({
- type: e.target.value,
- })
- }
- render() {
- return (
-
-
-
-
-
-
-
- )
- }
-}
-
-export default Chart
diff --git a/src/pages/chart/ECharts/index.less b/src/pages/chart/ECharts/index.less
deleted file mode 100644
index 7a42d68..0000000
--- a/src/pages/chart/ECharts/index.less
+++ /dev/null
@@ -1,34 +0,0 @@
-.chart {
- label {
- margin: 24px 0;
- display: block;
- font-size: 14px;
- }
-
- pre {
- padding: 16px;
- overflow: auto;
- font-size: 12px;
- line-height: 2;
- background-color: #f6f8fa;
- border-radius: 3px;
-
- code {
- display: inline;
- max-width: auto;
- padding: 0;
- margin: 0;
- overflow: visible;
- line-height: inherit;
- word-wrap: normal;
- background-color: transparent;
- border: 0;
- }
- }
-}
-
-:global {
- .ant-radio-wrapper {
- margin-bottom: 16px;
- }
-}
diff --git a/src/pages/chart/ECharts/theme/macarons.js b/src/pages/chart/ECharts/theme/macarons.js
deleted file mode 100644
index f0a700a..0000000
--- a/src/pages/chart/ECharts/theme/macarons.js
+++ /dev/null
@@ -1,188 +0,0 @@
-import * as echarts from 'echarts'
-
-let colorPalette = [
- '#2ec7c9',
- '#b6a2de',
- '#5ab1ef',
- '#ffb980',
- '#d87a80',
- '#8d98b3',
- '#e5cf0d',
- '#97b552',
- '#95706d',
- '#dc69aa',
- '#07a2a4',
- '#9a7fd1',
- '#588dd5',
- '#f5994e',
- '#c05050',
- '#59678c',
- '#c9ab00',
- '#7eb00a',
- '#6f5553',
- '#c14089',
- ]
-
- let theme = {
- color: colorPalette,
-
- title: {
- fontWeight: 'normal',
- color: '#008acd',
- },
-
- visualMap: {
- itemWidth: 15,
- color: ['#5ab1ef', '#e0ffff'],
- },
-
- toolbox: {
- iconStyle: {
- borderColor: colorPalette[0],
- },
- },
-
- tooltip: {
- backgroundColor: 'rgba(50,50,50,0.5)',
- axisPointer: {
- type: 'line',
- lineStyle: {
- color: '#008acd',
- },
- crossStyle: {
- color: '#008acd',
- },
- shadowStyle: {
- color: 'rgba(200,200,200,0.2)',
- },
- },
- },
-
- dataZoom: {
- dataBackgroundColor: '#efefff',
- fillerColor: 'rgba(182,162,222,0.2)',
- handleColor: '#008acd',
- },
-
- grid: {
- borderColor: '#eee',
- },
-
- categoryAxis: {
- axisLine: {
- lineStyle: {
- color: '#008acd',
- },
- },
- splitLine: {
- lineStyle: {
- color: ['#eee'],
- },
- },
- },
-
- valueAxis: {
- axisLine: {
- lineStyle: {
- color: '#008acd',
- },
- },
- splitArea: {
- show: true,
- areaStyle: {
- color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)'],
- },
- },
- splitLine: {
- lineStyle: {
- color: ['#eee'],
- },
- },
- },
-
- timeline: {
- lineStyle: {
- color: '#008acd',
- },
- controlStyle: {
- color: '#008acd',
- },
- emphasis: {
- controlStyle: { color: '#008acd' },
- },
- symbol: 'emptyCircle',
- symbolSize: 3,
- },
-
- line: {
- smooth: true,
- symbol: 'emptyCircle',
- symbolSize: 3,
- },
-
- candlestick: {
- itemStyle: {
- color: '#d87a80',
- color0: '#2ec7c9',
- lineStyle: {
- color: '#d87a80',
- color0: '#2ec7c9',
- },
- },
- },
-
- scatter: {
- symbol: 'circle',
- symbolSize: 4,
- },
-
- map: {
- label: { color: '#d87a80' },
- itemStyle: {
- borderColor: '#eee',
- areaColor: '#ddd',
- },
- emphasis: {
- itemStyle: {
- areaColor: '#fe994e',
- },
- },
- },
-
- graph: {
- color: colorPalette,
- },
-
- gauge: {
- axisLine: {
- lineStyle: {
- color: [[0.2, '#2ec7c9'], [0.8, '#5ab1ef'], [1, '#d87a80']],
- width: 10,
- },
- },
- axisTick: {
- splitNumber: 10,
- length: 15,
- lineStyle: {
- color: 'auto',
- },
- },
- splitLine: {
- length: 22,
- lineStyle: {
- color: 'auto',
- },
- },
- pointer: {
- width: 5,
- },
- },
- }
-
-if (echarts && typeof echarts.registerTheme === 'function') {
- echarts.registerTheme('macarons', theme)
-} else {
- // Console may be used only to report theme registration failure; suppress lint here
- // eslint-disable-next-line no-console
- if (typeof console !== 'undefined' && console.error) console.error('ECharts is not loaded, failed to register macarons theme')
-}
diff --git a/src/pages/chart/ECharts/theme/shine.js b/src/pages/chart/ECharts/theme/shine.js
deleted file mode 100644
index 59126b7..0000000
--- a/src/pages/chart/ECharts/theme/shine.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import * as echarts from 'echarts'
-
-let colorPalette = [
- '#c12e34',
- '#e6b600',
- '#0098d9',
- '#2b821d',
- '#005eaa',
- '#339ca8',
- '#cda819',
- '#32a487',
- ]
-
- let theme = {
- color: colorPalette,
-
- title: {
- fontWeight: 'normal',
- },
-
- visualMap: {
- color: ['#1790cf', '#a2d4e6'],
- },
-
- toolbox: {
- iconStyle: {
- borderColor: '#06467c',
- },
- },
-
- tooltip: {
- backgroundColor: 'rgba(0,0,0,0.6)',
- },
-
- dataZoom: {
- dataBackgroundColor: '#dedede',
- fillerColor: 'rgba(154,217,247,0.2)',
- handleColor: '#005eaa',
- },
-
- timeline: {
- lineStyle: {
- color: '#005eaa',
- },
- controlStyle: {
- color: '#005eaa',
- borderColor: '#005eaa',
- },
- },
-
- candlestick: {
- itemStyle: {
- color: '#c12e34',
- color0: '#2b821d',
- lineStyle: {
- width: 1,
- color: '#c12e34',
- color0: '#2b821d',
- },
- },
- },
-
- graph: {
- color: colorPalette,
- },
-
- map: {
- label: { color: '#c12e34' },
- itemStyle: {
- borderColor: '#eee',
- areaColor: '#ddd',
- },
- emphasis: {
- label: { color: '#c12e34' },
- itemStyle: {
- areaColor: '#e6b600',
- },
- },
- },
-
- gauge: {
- axisLine: {
- show: true,
- lineStyle: {
- color: [[0.2, '#2b821d'], [0.8, '#005eaa'], [1, '#c12e34']],
- width: 5,
- },
- },
- axisTick: {
- splitNumber: 10,
- length: 8,
- lineStyle: {
- color: 'auto',
- },
- },
- axisLabel: {
- color: 'auto',
- },
- splitLine: {
- length: 12,
- lineStyle: {
- color: 'auto',
- },
- },
- pointer: {
- length: '90%',
- width: 3,
- color: 'auto',
- },
- title: {
- color: '#333',
- },
- detail: {
- textStyle: {
- color: 'auto',
- },
- },
- },
- }
-if (echarts && typeof echarts.registerTheme === 'function') {
- echarts.registerTheme('shine', theme)
-} else {
- // Console may be used only to report theme registration failure; suppress lint here
- // eslint-disable-next-line no-console
- if (typeof console !== 'undefined' && console.error) console.error('ECharts is not loaded, failed to register shine theme')
-}
diff --git a/src/pages/chart/Recharts/AreaChartComponent.js b/src/pages/chart/Recharts/AreaChartComponent.js
deleted file mode 100644
index 519a245..0000000
--- a/src/pages/chart/Recharts/AreaChartComponent.js
+++ /dev/null
@@ -1,360 +0,0 @@
-import React from 'react'
-import { Row, Col, Card, Button } from 'antd'
-import * as d3 from 'd3-shape'
-import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'
-import Container from './Container'
-
-const data = [
- {
- name: 'Page A',
- uv: 4000,
- pv: 2400,
- amt: 2400,
- },
- {
- name: 'Page B',
- uv: 3000,
- pv: 1398,
- amt: 2210,
- },
- {
- name: 'Page C',
- uv: 2000,
- pv: 9800,
- amt: 2290,
- },
- {
- name: 'Page D',
- uv: 2780,
- pv: 3908,
- amt: 2000,
- },
- {
- name: 'Page E',
- uv: 1890,
- pv: 4800,
- amt: 2181,
- },
- {
- name: 'Page F',
- uv: 2390,
- pv: 3800,
- amt: 2500,
- },
- {
- name: 'Page G',
- uv: 3490,
- pv: 4300,
- amt: 2100,
- },
-]
-
-const mixData = [
- {
- name: 'Page A',
- uv: 4000,
- pv: 2400,
- amt: 2400,
- },
- {
- name: 'Page B',
- uv: 3000,
- pv: 1398,
- amt: 2210,
- },
- {
- name: 'Page C',
- uv: 2000,
- pv: 9800,
- amt: 2290,
- },
- {
- name: 'Page D',
- uv: 2780,
- pv: 3908,
- amt: 2000,
- },
- {
- name: 'Page E',
- uv: 1890,
- pv: 4800,
- amt: 2181,
- },
- {
- name: 'Page F',
- uv: 2390,
- pv: 3800,
- amt: 2500,
- },
- {
- name: 'Page G',
- uv: 3490,
- pv: 4300,
- amt: 2100,
- },
-]
-
-const percentData = [
- {
- month: '2015.01',
- a: 4000,
- b: 2400,
- c: 2400,
- },
- {
- month: '2015.02',
- a: 3000,
- b: 1398,
- c: 2210,
- },
- {
- month: '2015.03',
- a: 2000,
- b: 9800,
- c: 2290,
- },
- {
- month: '2015.04',
- a: 2780,
- b: 3908,
- c: 2000,
- },
- {
- month: '2015.05',
- a: 1890,
- b: 4800,
- c: 2181,
- },
- {
- month: '2015.06',
- a: 2390,
- b: 3800,
- c: 2500,
- },
- {
- month: '2015.07',
- a: 3490,
- b: 4300,
- c: 2100,
- },
-]
-
-const colProps = {
- lg: 12,
- md: 24,
-}
-
-const SimpleAreaChart = () => (
-
-
-
-
-
-
-
-
-
-)
-
-const StackedAreaChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-// StackedAreaChart
-
-const toPercent = (decimal, fixed = 0) => {
- return `${(decimal * 100).toFixed(fixed)}%`
-}
-
-const getPercent = (value, total) => {
- const ratio = total > 0 ? value / total : 0
-
- return toPercent(ratio, 2)
-}
-
-const renderTooltipContent = o => {
- const { payload, label } = o
- const total = payload.reduce((result, entry) => result + entry.value, 0)
-
- return (
-
-
{`${label} (Total: ${total})`}
-
- {payload.map((entry, index) => (
-
- {`${entry.name}: ${entry.value}(${getPercent(entry.value, total)})`}
-
- ))}
-
-
- )
-}
-
-const PercentAreaChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-// CardinalAreaChart
-const cardinal = d3.curveCardinal.tension(0.2)
-const CardinalAreaChart = () => (
-
-
-
-
-
-
-
-
-
-
-)
-
-const AreaChartPage = () => (
-
-
-
- Show More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export default AreaChartPage
diff --git a/src/pages/chart/Recharts/BarChartComponent.js b/src/pages/chart/Recharts/BarChartComponent.js
deleted file mode 100644
index e524ea7..0000000
--- a/src/pages/chart/Recharts/BarChartComponent.js
+++ /dev/null
@@ -1,260 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Row, Col, Card, Button } from 'antd'
-import {
- BarChart,
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
-} from 'recharts'
-import Container from './Container'
-
-const data = [
- {
- name: 'Page A',
- uv: 4000,
- pv: 2400,
- amt: 2400,
- },
- {
- name: 'Page B',
- uv: 3000,
- pv: 1398,
- amt: 2210,
- },
- {
- name: 'Page C',
- uv: 2000,
- pv: 9800,
- amt: 2290,
- },
- {
- name: 'Page D',
- uv: 2780,
- pv: 3908,
- amt: 2000,
- },
- {
- name: 'Page E',
- uv: 1890,
- pv: 4800,
- amt: 2181,
- },
- {
- name: 'Page F',
- uv: 2390,
- pv: 3800,
- amt: 2500,
- },
- {
- name: 'Page G',
- uv: 3490,
- pv: 4300,
- amt: 2100,
- },
-]
-
-const mixData = [
- {
- name: 'Page A',
- uv: 4000,
- female: 2400,
- male: 2400,
- },
- {
- name: 'Page B',
- uv: 3000,
- female: 1398,
- male: 2210,
- },
- {
- name: 'Page C',
- uv: 2000,
- female: 9800,
- male: 2290,
- },
- {
- name: 'Page D',
- uv: 2780,
- female: 3908,
- male: 2000,
- },
- {
- name: 'Page E',
- uv: 1890,
- female: 4800,
- male: 2181,
- },
- {
- name: 'Page F',
- uv: 2390,
- female: 3800,
- male: 2500,
- },
- {
- name: 'Page G',
- uv: 3490,
- female: 4300,
- male: 2100,
- },
-]
-const colProps = {
- lg: 12,
- md: 24,
-}
-
-const SimpleBarChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-const StackedBarChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-const MixBarChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-// CustomShapeBarChart
-const getPath = (x, y, width, height) => {
- return `M${x},${y + height}
- C${x + width / 3},${y + height} ${x + width / 2},${y + height / 3} ${x +
- width / 2}, ${y}
- C${x + width / 2},${y + height / 3} ${x + (2 * width) / 3},${y +
- height} ${x + width}, ${y + height}
- Z`
-}
-
-const TriangleBar = props => {
- const { fill, x, y, width, height } = props
- return
-}
-
-TriangleBar.propTypes = {
- fill: PropTypes.string,
- x: PropTypes.number,
- y: PropTypes.number,
- width: PropTypes.number,
- height: PropTypes.number,
-}
-
-const CustomShapeBarChart = () => (
-
-
-
-
-
- } label />
-
-
-)
-
-const BarChartPage = () => (
-
-
-
- Show More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export default BarChartPage
diff --git a/src/pages/chart/Recharts/Container.js b/src/pages/chart/Recharts/Container.js
deleted file mode 100644
index 5c19608..0000000
--- a/src/pages/chart/Recharts/Container.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { ResponsiveContainer } from 'recharts'
-import styles from './Container.less'
-
-const Container = ({
- children,
- ratio = 5 / 2,
- minHeight = 250,
- maxHeight = 350,
-}) => (
-
-)
-
-Container.propTypes = {
- children: PropTypes.element.isRequired,
- ratio: PropTypes.number,
- minHeight: PropTypes.number,
- maxHeight: PropTypes.number,
-}
-
-export default Container
diff --git a/src/pages/chart/Recharts/Container.less b/src/pages/chart/Recharts/Container.less
deleted file mode 100644
index daa5c27..0000000
--- a/src/pages/chart/Recharts/Container.less
+++ /dev/null
@@ -1,20 +0,0 @@
-.container {
- width: 100%;
- position: relative;
- display: inline-block;
-
- :global {
- .recharts-responsive-container {
- width: e('calc(100% + 56px)') !important;
- margin-left: -32px;
- }
- }
-}
-
-.content {
- position: absolute;
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
-}
diff --git a/src/pages/chart/Recharts/LineChartComponent.js b/src/pages/chart/Recharts/LineChartComponent.js
deleted file mode 100644
index a249cdf..0000000
--- a/src/pages/chart/Recharts/LineChartComponent.js
+++ /dev/null
@@ -1,261 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Row, Col, Card, Button } from 'antd'
-import {
- LineChart,
- Line,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
-} from 'recharts'
-import Container from './Container'
-
-const data = [
- {
- name: 'Page A',
- uv: 4000,
- pv: 2400,
- amt: 2400,
- },
- {
- name: 'Page B',
- uv: 3000,
- pv: 1398,
- amt: 2210,
- },
- {
- name: 'Page C',
- uv: 2000,
- pv: 9800,
- amt: 2290,
- },
- {
- name: 'Page D',
- uv: 2780,
- pv: 3908,
- amt: 2000,
- },
- {
- name: 'Page E',
- uv: 1890,
- pv: 4800,
- amt: 2181,
- },
- {
- name: 'Page F',
- uv: 2390,
- pv: 3800,
- amt: 2500,
- },
- {
- name: 'Page G',
- uv: 3490,
- pv: 4300,
- amt: 2100,
- },
-]
-
-const colProps = {
- lg: 12,
- md: 24,
-}
-
-const SimpleLineChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-const VerticalLineChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-const DashedLineChart = () => (
-
-
-
-
-
-
-
-
-
-
-
-)
-
-// CustomizedDotLineChart
-const CustomizedDot = ({ cx, cy, payload }) => {
- if (payload.value > 2500) {
- return (
-
-
-
- )
- }
-
- return (
-
-
-
- )
-}
-
-CustomizedDot.propTypes = {
- cx: PropTypes.number,
- cy: PropTypes.number,
- payload: PropTypes.object,
-}
-
-const CustomizedDotLineChart = () => (
-
-
-
-
-
-
-
- }
- />
-
-
-
-)
-
-const LineChartPage = () => (
-
-
-
- Show More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export default LineChartPage
diff --git a/src/pages/chart/Recharts/ReChartsComponent.js b/src/pages/chart/Recharts/ReChartsComponent.js
deleted file mode 100644
index 9ed7acc..0000000
--- a/src/pages/chart/Recharts/ReChartsComponent.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-
-import AreaChartComponent from './AreaChartComponent'
-import BarChartComponent from './BarChartComponent'
-import LineChartComponent from './LineChartComponent'
-
-const ReChartsComponent = ({ type }) => {
- if (type === 'areaChart') return
- if (type === 'barChart') return
- return
-}
-
-ReChartsComponent.propTypes = {
- type: PropTypes.string,
-}
-
-export default ReChartsComponent
diff --git a/src/pages/chart/Recharts/index.js b/src/pages/chart/Recharts/index.js
deleted file mode 100644
index fec6ce2..0000000
--- a/src/pages/chart/Recharts/index.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react'
-import { Radio } from 'antd'
-import { Page } from 'components'
-import ReChartsComponent from './ReChartsComponent'
-import styles from './index.less'
-
-const RadioGroup = Radio.Group
-
-const chartList = [
- {
- label: 'lineChart',
- value: 'lineChart',
- },
- {
- label: 'barChart',
- value: 'barChart',
- },
- {
- label: 'areaChart',
- value: 'areaChart',
- },
-]
-
-class Chart extends React.Component {
- constructor() {
- super()
- this.state = {
- type: '',
- }
- this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
- }
- handleRadioGroupChange(e) {
- this.setState({
- type: e.target.value,
- })
- }
- render() {
- return (
-
-
-
-
-
-
- )
- }
-}
-
-export default Chart
diff --git a/src/pages/chart/Recharts/index.less b/src/pages/chart/Recharts/index.less
deleted file mode 100644
index 169053a..0000000
--- a/src/pages/chart/Recharts/index.less
+++ /dev/null
@@ -1,8 +0,0 @@
-.chart {
- :global {
- .ant-card {
- overflow: hidden;
- margin-bottom: 24px;
- }
- }
-}
diff --git a/src/pages/chart/highCharts/HighChartsComponent.js b/src/pages/chart/highCharts/HighChartsComponent.js
deleted file mode 100644
index 520e08a..0000000
--- a/src/pages/chart/highCharts/HighChartsComponent.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-
-import HighstockComponent from './HighstockComponent'
-import HighmapsComponent from './HighmapsComponent'
-import HighMoreComponent from './HighMoreComponent'
-
-const HighChartsComponent = ({ type }) => {
- if (type === 'Highmaps') return
- if (type === 'HighMore') return
- return
-}
-
-HighChartsComponent.propTypes = {
- type: PropTypes.string,
-}
-
-export default HighChartsComponent
diff --git a/src/pages/chart/highCharts/HighMoreComponent.js b/src/pages/chart/highCharts/HighMoreComponent.js
deleted file mode 100644
index 6c0db01..0000000
--- a/src/pages/chart/highCharts/HighMoreComponent.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import React from 'react'
-import Highcharts from 'highcharts'
-import HighchartsMore from 'highcharts/highcharts-more'
-import Exporting from 'highcharts/modules/exporting'
-import Accessibility from 'highcharts/modules/accessibility'
-import HighchartsReact from 'highcharts-react-official'
-
-HighchartsMore(Highcharts)
-Exporting(Highcharts)
-Accessibility(Highcharts)
-
-const config = {
- chart: {
- polar: true,
- },
- xAxis: {
- categories: [
- 'Jan',
- 'Feb',
- 'Mar',
- 'Apr',
- 'May',
- 'Jun',
- 'Jul',
- 'Aug',
- 'Sep',
- 'Oct',
- 'Nov',
- 'Dec',
- ],
- },
- series: [
- {
- data: [
- 29.9,
- 71.5,
- 106.4,
- 129.2,
- 144.0,
- 176.0,
- 135.6,
- 148.5,
- 216.4,
- 194.1,
- 95.6,
- 54.4,
- ],
- },
- ],
-}
-
-const HighMoreComponent = () => {
- return
-}
-
-export default HighMoreComponent
diff --git a/src/pages/chart/highCharts/HighmapsComponent.js b/src/pages/chart/highCharts/HighmapsComponent.js
deleted file mode 100644
index dd37666..0000000
--- a/src/pages/chart/highCharts/HighmapsComponent.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import React from 'react'
-import Highcharts from 'highcharts/highmaps'
-import Accessibility from 'highcharts/modules/accessibility'
-import HighchartsReact from 'highcharts-react-official'
-import maps from './mapdata/europe'
-
-const config = {
- chart: {
- spacingBottom: 20,
- },
- title: {
- text: 'Europe time zones',
- },
-
- legend: {
- enabled: true,
- },
-
- plotOptions: {
- map: {
- allAreas: false,
- joinBy: ['iso-a2', 'code'],
- dataLabels: {
- enabled: true,
- color: 'white',
- style: {
- fontWeight: 'bold',
- },
- },
- mapData: maps,
- tooltip: {
- headerFormat: '',
- pointFormat: '{point.name}: {series.name} ',
- },
- },
- },
-
- series: [
- {
- name: 'UTC',
- data: ['IE', 'IS', 'GB', 'PT'].map(code => {
- return { code }
- }),
- },
- {
- name: 'UTC + 1',
- data: [
- 'NO',
- 'SE',
- 'DK',
- 'DE',
- 'NL',
- 'BE',
- 'LU',
- 'ES',
- 'FR',
- 'PL',
- 'CZ',
- 'AT',
- 'CH',
- 'LI',
- 'SK',
- 'HU',
- 'SI',
- 'IT',
- 'SM',
- 'HR',
- 'BA',
- 'YF',
- 'ME',
- 'AL',
- 'MK',
- ].map(code => {
- return { code }
- }),
- },
- ],
-}
-
-Accessibility(Highcharts)
-
-const HighmapsComponent = () => {
- return
-}
-export default HighmapsComponent
diff --git a/src/pages/chart/highCharts/HighstockComponent.js b/src/pages/chart/highCharts/HighstockComponent.js
deleted file mode 100644
index a22878c..0000000
--- a/src/pages/chart/highCharts/HighstockComponent.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from 'react'
-import Highcharts from 'highcharts/highstock'
-import Accessibility from 'highcharts/modules/accessibility'
-import HighchartsReact from 'highcharts-react-official'
-
-const data = [
- [1220832000000, 22.56],
- [1220918400000, 21.67],
- [1221004800000, 21.66],
- [1221091200000, 21.81],
- [1221177600000, 21.28],
- [1221436800000, 20.05],
- [1221523200000, 19.98],
- [1221609600000, 18.26],
- [1221696000000, 19.16],
- [1221782400000, 20.13],
- [1222041600000, 18.72],
- [1222128000000, 18.12],
- [1222214400000, 18.39],
- [1222300800000, 18.85],
- [1222387200000, 18.32],
- [1222646400000, 15.04],
- [1222732800000, 16.24],
- [1222819200000, 15.59],
- [1222905600000, 14.3],
- [1222992000000, 13.87],
- [1223251200000, 14.02],
- [1223337600000, 12.74],
- [1223424000000, 12.83],
- [1223510400000, 12.68],
- [1223596800000, 13.8],
- [1223856000000, 15.75],
- [1223942400000, 14.87],
- [1224028800000, 13.99],
- [1224115200000, 14.56],
- [1224201600000, 13.91],
- [1224460800000, 14.06],
- [1224547200000, 13.07],
- [1224633600000, 13.84],
- [1224720000000, 14.03],
- [1224806400000, 13.77],
- [1225065600000, 13.16],
- [1225152000000, 14.27],
- [1225238400000, 14.94],
- [1225324800000, 15.86],
- [1225411200000, 15.37],
- [1225670400000, 15.28],
- [1225756800000, 15.86],
- [1225843200000, 14.76],
- [1225929600000, 14.16],
- [1226016000000, 14.03],
- [1226275200000, 13.7],
- [1226361600000, 13.54],
- [1226448000000, 12.87],
- [1226534400000, 13.78],
- [1226620800000, 12.89],
- [1226880000000, 12.59],
- [1226966400000, 12.84],
- [1227052800000, 12.33],
- [1227139200000, 11.5],
- [1227225600000, 11.8],
- [1227484800000, 13.28],
- [1227571200000, 12.97],
- [1227657600000, 13.57],
- [1227830400000, 13.24],
- [1228089600000, 12.7],
- [1228176000000, 13.21],
- [1228262400000, 13.7],
- [1228348800000, 13.06],
- [1228435200000, 13.43],
- [1228694400000, 14.25],
- [1228780800000, 14.29],
- [1228867200000, 14.03],
- [1228953600000, 13.57],
- [1229040000000, 14.04],
- [1229299200000, 13.54],
-]
-
-const config = {
- rangeSelector: {
- selected: 1,
- },
- title: {
- text: 'AAPL Stock Price',
- },
- series: [
- {
- name: 'AAPL',
- data,
- tooltip: {
- valueDecimals: 2,
- },
- },
- ],
-}
-
-const HighstockComponent = () => {
- return (
-
- )
-}
-
-Accessibility(Highcharts)
-
-export default HighstockComponent
diff --git a/src/pages/chart/highCharts/index.js b/src/pages/chart/highCharts/index.js
deleted file mode 100644
index 44a80de..0000000
--- a/src/pages/chart/highCharts/index.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react'
-import { Radio } from 'antd'
-import { Page } from 'components'
-import HighChartsComponent from './HighChartsComponent'
-import styles from './index.less'
-
-const RadioGroup = Radio.Group
-
-const chartList = [
- {
- label: 'Highstock',
- value: 'Highstock',
- },
- {
- label: 'Highmaps',
- value: 'Highmaps',
- },
- {
- label: 'HighMore',
- value: 'HighMore',
- },
-]
-
-class Chart extends React.Component {
- constructor() {
- super()
- this.state = {
- type: '',
- }
- this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
- }
- handleRadioGroupChange(e) {
- this.setState({
- type: e.target.value,
- })
- }
- render() {
- return (
-
-
-
-
-
-
- )
- }
-}
-
-export default Chart
diff --git a/src/pages/chart/highCharts/index.less b/src/pages/chart/highCharts/index.less
deleted file mode 100644
index 2926f77..0000000
--- a/src/pages/chart/highCharts/index.less
+++ /dev/null
@@ -1,7 +0,0 @@
-.chart {
- :global {
- .ant-radio-wrapper {
- margin-bottom: 16px;
- }
- }
-}
diff --git a/src/pages/chart/highCharts/mapdata/europe.js b/src/pages/chart/highCharts/mapdata/europe.js
deleted file mode 100644
index 95a13c9..0000000
--- a/src/pages/chart/highCharts/mapdata/europe.js
+++ /dev/null
@@ -1,9162 +0,0 @@
-module.exports = {
- title: 'Europe',
- version: '1.1.2',
- type: 'FeatureCollection',
- copyright: 'Copyright (c) 2015 Highsoft AS, Based on data from Natural Earth',
- copyrightShort: 'Natural Earth',
- copyrightUrl: 'http://www.naturalearthdata.com',
- crs: { type: 'name', properties: { name: 'urn:ogc:def:crs:EPSG:102014' } },
- 'hc-transform': {
- default: {
- crs:
- '+proj=lcc +lat_1=43 +lat_2=62 +lat_0=30 +lon_0=10 +x_0=0 +y_0=0 +ellps=intl +units=m +no_defs',
- scale: 0.000169255094964,
- jsonres: 15.5,
- jsonmarginX: -999,
- jsonmarginY: 9851.0,
- xoffset: -1706316.2597,
- yoffset: 4824494.18154,
- },
- },
- features: [
- {
- type: 'Feature',
- id: 'DK',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.19,
- 'hc-middle-y': 0.44,
- 'hc-key': 'dk',
- 'hc-a2': 'DK',
- name: 'Denmark',
- labelrank: '4',
- 'country-abbrev': 'Den.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'DNK',
- 'iso-a2': 'DK',
- 'woe-id': '23424796',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [3686, 4530],
- [3743, 4490],
- [3764, 4508],
- [3787, 4471],
- [3773, 4443],
- [3724, 4436],
- [3644, 4481],
- [3658, 4526],
- [3686, 4530],
- ],
- ],
- [
- [
- [3536, 4512],
- [3564, 4499],
- [3541, 4493],
- [3513, 4524],
- [3536, 4512],
- ],
- ],
- [
- [
- [3898, 4549],
- [3847, 4516],
- [3831, 4533],
- [3864, 4557],
- [3898, 4549],
- ],
- ],
- [
- [
- [3443, 4565],
- [3482, 4530],
- [3472, 4501],
- [3447, 4513],
- [3443, 4565],
- ],
- ],
- [
- [
- [3616, 4524],
- [3599, 4467],
- [3584, 4496],
- [3635, 4587],
- [3616, 4524],
- ],
- ],
- [
- [
- [4282, 4636],
- [4328, 4610],
- [4317, 4568],
- [4251, 4596],
- [4260, 4656],
- [4282, 4636],
- ],
- ],
- [
- [
- [3217, 4671],
- [3225, 4667],
- [3223, 4642],
- [3210, 4667],
- [3217, 4671],
- ],
- ],
- [
- [
- [3533, 4717],
- [3608, 4645],
- [3599, 4563],
- [3567, 4550],
- [3511, 4568],
- [3458, 4614],
- [3459, 4642],
- [3424, 4684],
- [3533, 4717],
- ],
- ],
- [
- [
- [3899, 4708],
- [3889, 4727],
- [3902, 4751],
- [3914, 4724],
- [3899, 4708],
- ],
- ],
- [
- [
- [3578, 4795],
- [3579, 4771],
- [3562, 4769],
- [3572, 4800],
- [3578, 4795],
- ],
- ],
- [
- [
- [3306, 5107],
- [3300, 5063],
- [3263, 5028],
- [3247, 5060],
- [3264, 5089],
- [3306, 5107],
- ],
- ],
- [
- [
- [3664, 5214],
- [3644, 5204],
- [3639, 5178],
- [3611, 5196],
- [3664, 5214],
- ],
- ],
- [
- [
- [3282, 4516],
- [3255, 4513],
- [3260, 4582],
- [3227, 4563],
- [3225, 4595],
- [3261, 4584],
- [3251, 4670],
- [3201, 4707],
- [3166, 4708],
- [3184, 4779],
- [3216, 4802],
- [3200, 4855],
- [3172, 4868],
- [3178, 4993],
- [3188, 5027],
- [3208, 5000],
- [3249, 4999],
- [3274, 4971],
- [3277, 5015],
- [3312, 5063],
- [3347, 5035],
- [3326, 5010],
- [3362, 5013],
- [3345, 5088],
- [3389, 5126],
- [3417, 5111],
- [3444, 5148],
- [3362, 5120],
- [3338, 5136],
- [3269, 5107],
- [3237, 5058],
- [3245, 5003],
- [3199, 5057],
- [3260, 5156],
- [3385, 5166],
- [3411, 5189],
- [3472, 5289],
- [3508, 5292],
- [3570, 5336],
- [3544, 5283],
- [3563, 5244],
- [3562, 5187],
- [3548, 5172],
- [3519, 5092],
- [3535, 4992],
- [3603, 4985],
- [3632, 4960],
- [3598, 4893],
- [3578, 4899],
- [3563, 4859],
- [3558, 4910],
- [3542, 4916],
- [3513, 4872],
- [3517, 4805],
- [3460, 4782],
- [3487, 4757],
- [3444, 4736],
- [3454, 4722],
- [3395, 4683],
- [3430, 4613],
- [3394, 4588],
- [3434, 4536],
- [3435, 4494],
- [3409, 4521],
- [3384, 4487],
- [3384, 4487],
- [3282, 4516],
- ],
- ],
- [
- [
- [3717, 4815],
- [3747, 4806],
- [3772, 4741],
- [3805, 4817],
- [3779, 4826],
- [3839, 4872],
- [3901, 4850],
- [3884, 4814],
- [3901, 4753],
- [3845, 4704],
- [3842, 4672],
- [3879, 4649],
- [3871, 4621],
- [3837, 4612],
- [3834, 4545],
- [3818, 4538],
- [3838, 4500],
- [3805, 4457],
- [3768, 4508],
- [3790, 4538],
- [3750, 4596],
- [3685, 4601],
- [3656, 4648],
- [3675, 4656],
- [3665, 4703],
- [3634, 4751],
- [3681, 4753],
- [3717, 4786],
- [3717, 4815],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'FO',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.56,
- 'hc-middle-y': 0.16,
- 'hc-key': 'fo',
- 'hc-a2': 'FO',
- name: 'Faroe Islands',
- labelrank: '6',
- 'country-abbrev': 'Faeroe Is.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'FRO',
- 'iso-a2': 'FO',
- 'woe-id': '23424816',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [1151, 6735],
- [1169, 6717],
- [1165, 6663],
- [1146, 6700],
- [1151, 6735],
- ],
- ],
- [
- [
- [1176, 6812],
- [1202, 6782],
- [1190, 6763],
- [1169, 6796],
- [1176, 6812],
- ],
- ],
- [
- [
- [1150, 6888],
- [1163, 6872],
- [1139, 6859],
- [1115, 6902],
- [1150, 6888],
- ],
- ],
- [
- [
- [1158, 6940],
- [1202, 6846],
- [1193, 6819],
- [1166, 6880],
- [1143, 6903],
- [1158, 6940],
- ],
- ],
- [
- [
- [1199, 6933],
- [1230, 6870],
- [1208, 6857],
- [1181, 6898],
- [1172, 6936],
- [1199, 6933],
- ],
- ],
- [
- [
- [1236, 6908],
- [1230, 6914],
- [1227, 6927],
- [1232, 6941],
- [1236, 6908],
- ],
- ],
- [
- [
- [1226, 6902],
- [1222, 6904],
- [1215, 6942],
- [1217, 6946],
- [1226, 6902],
- ],
- ],
- [
- [
- [1254, 6915],
- [1248, 6943],
- [1254, 6940],
- [1257, 6921],
- [1255, 6914],
- [1263, 6903],
- [1246, 6901],
- [1239, 6895],
- [1243, 6931],
- [1254, 6915],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'HR',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.09,
- 'hc-middle-y': 0.39,
- 'hc-key': 'hr',
- 'hc-a2': 'HR',
- name: 'Croatia',
- labelrank: '6',
- 'country-abbrev': 'Cro.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'HRV',
- 'iso-a2': 'HR',
- 'woe-id': '23424843',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [4965, 1080],
- [4964, 1071],
- [4945, 1066],
- [4938, 1078],
- [4965, 1080],
- ],
- ],
- [
- [
- [5060, 1097],
- [5137, 1081],
- [5142, 1075],
- [5062, 1089],
- [5060, 1097],
- ],
- ],
- [
- [
- [4924, 1135],
- [4980, 1143],
- [5017, 1129],
- [4944, 1116],
- [4924, 1135],
- ],
- ],
- [
- [
- [4799, 1153],
- [4804, 1142],
- [4785, 1134],
- [4768, 1148],
- [4799, 1153],
- ],
- ],
- [
- [
- [4873, 1205],
- [4915, 1189],
- [5010, 1189],
- [4904, 1178],
- [4834, 1192],
- [4873, 1205],
- ],
- ],
- [
- [
- [4922, 1243],
- [4925, 1221],
- [4841, 1228],
- [4840, 1250],
- [4922, 1243],
- ],
- ],
- [
- [
- [4813, 1251],
- [4834, 1236],
- [4825, 1233],
- [4794, 1244],
- [4813, 1251],
- ],
- ],
- [
- [
- [4621, 1380],
- [4603, 1384],
- [4580, 1409],
- [4607, 1399],
- [4621, 1380],
- ],
- ],
- [
- [
- [4572, 1425],
- [4565, 1419],
- [4550, 1431],
- [4536, 1450],
- [4572, 1425],
- ],
- ],
- [
- [
- [4496, 1441],
- [4573, 1380],
- [4557, 1376],
- [4526, 1418],
- [4496, 1441],
- ],
- ],
- [
- [
- [4482, 1574],
- [4494, 1578],
- [4572, 1505],
- [4495, 1543],
- [4482, 1574],
- ],
- ],
- [
- [
- [4392, 1569],
- [4391, 1562],
- [4374, 1601],
- [4388, 1593],
- [4392, 1569],
- ],
- ],
- [
- [
- [4458, 1644],
- [4474, 1623],
- [4481, 1604],
- [4444, 1643],
- [4458, 1644],
- ],
- ],
- [
- [
- [4398, 1672],
- [4394, 1585],
- [4363, 1666],
- [4381, 1672],
- [4355, 1720],
- [4372, 1729],
- [4398, 1672],
- ],
- ],
- [
- [
- [4412, 1758],
- [4435, 1713],
- [4468, 1683],
- [4386, 1710],
- [4412, 1758],
- ],
- ],
- [
- [
- [5311, 1013],
- [5150, 1111],
- [5070, 1120],
- [5023, 1159],
- [5115, 1130],
- [5161, 1114],
- [5293, 1053],
- [5298, 1030],
- [5311, 1013],
- ],
- ],
- [
- [
- [5272, 2037],
- [5265, 2001],
- [5308, 1936],
- [5313, 1878],
- [5390, 1853],
- [5399, 1831],
- [5332, 1816],
- [5333, 1734],
- [5308, 1729],
- [5259, 1787],
- [5229, 1777],
- [5164, 1799],
- [5120, 1791],
- [5085, 1763],
- [5051, 1788],
- [5030, 1773],
- [4966, 1787],
- [4950, 1773],
- [4893, 1807],
- [4867, 1774],
- [4805, 1774],
- [4781, 1716],
- [4754, 1721],
- [4705, 1771],
- [4662, 1750],
- [4668, 1659],
- [4747, 1571],
- [4775, 1482],
- [4881, 1385],
- [4890, 1365],
- [4976, 1298],
- [5010, 1291],
- [5019, 1256],
- [5062, 1201],
- [5095, 1184],
- [5098, 1143],
- [4981, 1218],
- [4936, 1263],
- [4860, 1282],
- [4742, 1274],
- [4729, 1323],
- [4625, 1387],
- [4544, 1481],
- [4583, 1510],
- [4492, 1601],
- [4491, 1679],
- [4468, 1723],
- [4415, 1769],
- [4362, 1786],
- [4296, 1624],
- [4219, 1709],
- [4192, 1822],
- [4209, 1816],
- [4271, 1801],
- [4286, 1828],
- [4369, 1822],
- [4403, 1876],
- [4428, 1842],
- [4525, 1818],
- [4570, 1837],
- [4542, 1900],
- [4581, 1934],
- [4624, 1943],
- [4625, 2002],
- [4602, 2023],
- [4614, 2053],
- [4734, 2110],
- [4722, 2143],
- [4777, 2150],
- [4851, 2116],
- [4877, 2075],
- [4924, 2054],
- [4959, 2009],
- [5008, 2009],
- [5070, 1968],
- [5138, 1979],
- [5180, 1971],
- [5227, 2026],
- [5272, 2037],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'NL',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.34,
- 'hc-middle-y': 0.59,
- 'hc-key': 'nl',
- 'hc-a2': 'NL',
- name: 'Netherlands',
- labelrank: '5',
- 'country-abbrev': 'Neth.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'NLD',
- 'iso-a2': 'NL',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [2408, 3636],
- [2399, 3614],
- [2350, 3647],
- [2395, 3648],
- [2408, 3636],
- ],
- ],
- [
- [
- [2593, 4028],
- [2574, 3998],
- [2561, 4015],
- [2588, 4051],
- [2593, 4028],
- ],
- ],
- [
- [
- [2603, 4061],
- [2593, 4060],
- [2613, 4079],
- [2625, 4085],
- [2603, 4061],
- ],
- ],
- [
- [
- [2691, 4107],
- [2652, 4094],
- [2648, 4104],
- [2719, 4119],
- [2691, 4107],
- ],
- ],
- [
- [
- [2776, 4121],
- [2783, 4117],
- [2735, 4111],
- [2734, 4122],
- [2776, 4121],
- ],
- ],
- [
- [
- [2817, 4118],
- [2812, 4122],
- [2827, 4129],
- [2847, 4126],
- [2817, 4118],
- ],
- ],
- [
- [
- [2280, 3553],
- [2316, 3561],
- [2371, 3536],
- [2395, 3553],
- [2437, 3537],
- [2380, 3495],
- [2330, 3523],
- [2285, 3515],
- [2280, 3553],
- ],
- ],
- [
- [
- [2444, 3537],
- [2389, 3565],
- [2367, 3550],
- [2316, 3574],
- [2305, 3602],
- [2372, 3613],
- [2422, 3559],
- [2444, 3580],
- [2400, 3604],
- [2436, 3629],
- [2402, 3678],
- [2414, 3716],
- [2438, 3721],
- [2510, 3811],
- [2565, 3989],
- [2593, 3969],
- [2677, 4019],
- [2717, 4076],
- [2786, 4101],
- [2916, 4110],
- [2993, 4045],
- [2988, 3965],
- [2969, 3933],
- [2959, 3874],
- [2912, 3874],
- [2897, 3827],
- [2945, 3818],
- [2952, 3754],
- [2887, 3701],
- [2910, 3678],
- [2832, 3644],
- [2788, 3666],
- [2751, 3642],
- [2779, 3594],
- [2794, 3519],
- [2766, 3475],
- [2780, 3452],
- [2737, 3419],
- [2761, 3382],
- [2746, 3338],
- [2690, 3344],
- [2723, 3456],
- [2660, 3498],
- [2614, 3492],
- [2581, 3557],
- [2561, 3534],
- [2537, 3566],
- [2511, 3546],
- [2486, 3564],
- [2467, 3530],
- [2444, 3537],
- ],
- [
- [2720, 3812],
- [2735, 3822],
- [2736, 3825],
- [2736, 3825],
- [2736, 3825],
- [2750, 3836],
- [2750, 3869],
- [2717, 3876],
- [2748, 3862],
- [2736, 3825],
- [2736, 3825],
- [2736, 3825],
- [2720, 3812],
- [2720, 3812],
- [2720, 3812],
- [2720, 3812],
- [2670, 3782],
- [2621, 3817],
- [2717, 3877],
- [2712, 3924],
- [2732, 3940],
- [2678, 3949],
- [2676, 4013],
- [2616, 3976],
- [2626, 3929],
- [2657, 3907],
- [2608, 3887],
- [2614, 3833],
- [2591, 3815],
- [2674, 3776],
- [2720, 3812],
- [2720, 3812],
- [2720, 3812],
- [2720, 3812],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'EE',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.6,
- 'hc-middle-y': 0.53,
- 'hc-key': 'ee',
- 'hc-a2': 'EE',
- name: 'Estonia',
- labelrank: '6',
- 'country-abbrev': 'Est.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'EST',
- 'iso-a2': 'EE',
- 'woe-id': '23424805',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [5483, 5775],
- [5469, 5746],
- [5441, 5761],
- [5452, 5784],
- [5483, 5775],
- ],
- ],
- [
- [
- [5440, 5891],
- [5460, 5893],
- [5468, 5876],
- [5435, 5869],
- [5440, 5891],
- ],
- ],
- [
- [
- [5370, 5868],
- [5403, 5866],
- [5426, 5828],
- [5383, 5810],
- [5362, 5771],
- [5337, 5813],
- [5275, 5835],
- [5334, 5850],
- [5348, 5887],
- [5370, 5868],
- ],
- ],
- [
- [
- [6143, 6139],
- [6134, 6134],
- [6119, 6126],
- [6130, 6125],
- [6130, 6125],
- [6131, 6125],
- [6133, 6126],
- [6124, 6119],
- [6106, 6106],
- [6108, 6072],
- [6106, 6046],
- [6102, 6021],
- [6074, 6020],
- [6012, 5988],
- [6005, 5937],
- [6044, 5907],
- [6076, 5839],
- [6106, 5812],
- [6161, 5739],
- [6194, 5705],
- [6159, 5682],
- [6151, 5595],
- [6071, 5605],
- [6022, 5561],
- [5934, 5617],
- [5926, 5637],
- [5861, 5642],
- [5801, 5677],
- [5699, 5617],
- [5669, 5588],
- [5681, 5651],
- [5674, 5732],
- [5642, 5736],
- [5618, 5688],
- [5557, 5706],
- [5535, 5759],
- [5512, 5760],
- [5504, 5815],
- [5545, 5818],
- [5543, 5838],
- [5488, 5817],
- [5479, 5858],
- [5495, 5879],
- [5471, 5891],
- [5470, 5946],
- [5555, 5992],
- [5541, 6009],
- [5694, 6088],
- [5735, 6082],
- [5770, 6142],
- [5882, 6132],
- [5912, 6141],
- [5940, 6125],
- [6095, 6143],
- [6111, 6169],
- [6126, 6160],
- [6143, 6139],
- ],
- ],
- [
- [
- [5280, 5633],
- [5297, 5662],
- [5272, 5700],
- [5306, 5706],
- [5369, 5757],
- [5380, 5746],
- [5417, 5762],
- [5487, 5731],
- [5463, 5728],
- [5448, 5692],
- [5403, 5642],
- [5351, 5628],
- [5342, 5561],
- [5313, 5556],
- [5336, 5608],
- [5280, 5633],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'BG',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.54,
- 'hc-middle-y': 0.49,
- 'hc-key': 'bg',
- 'hc-a2': 'BG',
- name: 'Bulgaria',
- labelrank: '4',
- 'country-abbrev': 'Bulg.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'BGR',
- 'iso-a2': 'BG',
- 'woe-id': '23424771',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [7353, 1794],
- [7373, 1738],
- [7359, 1683],
- [7291, 1678],
- [7265, 1596],
- [7264, 1551],
- [7287, 1467],
- [7255, 1461],
- [7212, 1374],
- [7250, 1382],
- [7288, 1337],
- [7352, 1291],
- [7366, 1265],
- [7320, 1259],
- [7271, 1219],
- [7200, 1259],
- [7149, 1241],
- [7136, 1215],
- [7058, 1175],
- [7060, 1145],
- [7026, 1104],
- [6967, 1083],
- [7003, 1040],
- [7004, 992],
- [6956, 965],
- [6884, 953],
- [6831, 918],
- [6735, 946],
- [6714, 926],
- [6671, 941],
- [6645, 975],
- [6547, 945],
- [6462, 882],
- [6390, 877],
- [6357, 845],
- [6313, 844],
- [6303, 916],
- [6313, 957],
- [6262, 1037],
- [6189, 1058],
- [6140, 1104],
- [6170, 1136],
- [6144, 1181],
- [6146, 1270],
- [6192, 1283],
- [6229, 1382],
- [6118, 1443],
- [6066, 1533],
- [6067, 1588],
- [6105, 1620],
- [6108, 1660],
- [6115, 1666],
- [6116, 1666],
- [6193, 1640],
- [6168, 1606],
- [6183, 1561],
- [6264, 1592],
- [6444, 1587],
- [6512, 1615],
- [6610, 1620],
- [6698, 1611],
- [6780, 1652],
- [6838, 1749],
- [7006, 1837],
- [7055, 1833],
- [7089, 1809],
- [7144, 1825],
- [7163, 1808],
- [7199, 1830],
- [7223, 1793],
- [7278, 1781],
- [7353, 1794],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'ES',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.38,
- 'hc-middle-y': 0.53,
- 'hc-key': 'es',
- 'hc-a2': 'ES',
- name: 'Spain',
- labelrank: '2',
- 'country-abbrev': 'Sp.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'ESP',
- 'iso-a2': 'ES',
- 'woe-id': '23424950',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [[[1502, -54], [1511, -71], [1486, -84], [1488, -64], [1502, -54]]],
- [
- [
- [1546, 46],
- [1554, 23],
- [1497, -29],
- [1456, -3],
- [1495, 44],
- [1546, 46],
- ],
- ],
- [
- [
- [1936, 261],
- [1927, 203],
- [1997, 190],
- [1929, 81],
- [1890, 58],
- [1826, 103],
- [1806, 154],
- [1767, 130],
- [1740, 175],
- [1879, 250],
- [1936, 261],
- ],
- ],
- [
- [
- [2150, 269],
- [2183, 239],
- [2186, 195],
- [2124, 231],
- [2082, 238],
- [2083, 271],
- [2150, 269],
- ],
- ],
- [
- [
- [-240, -615],
- [-227, -613],
- [-212, -627],
- [-226, -637],
- [-240, -615],
- ],
- ],
- [
- [
- [1743, 1010],
- [1741, 1016],
- [1746, 1022],
- [1751, 1009],
- [1743, 1010],
- ],
- ],
- [
- [
- [-212, -550],
- [-280, -577],
- [-390, -461],
- [-390, -378],
- [-423, -351],
- [-410, -302],
- [-426, -252],
- [-503, -171],
- [-621, -134],
- [-621, -20],
- [-559, 40],
- [-533, 84],
- [-473, 83],
- [-444, 129],
- [-481, 131],
- [-520, 224],
- [-479, 302],
- [-423, 324],
- [-393, 365],
- [-430, 404],
- [-449, 478],
- [-438, 511],
- [-476, 588],
- [-361, 562],
- [-304, 645],
- [-328, 703],
- [-262, 740],
- [-270, 768],
- [-238, 881],
- [-249, 936],
- [-220, 941],
- [-168, 993],
- [-134, 993],
- [-54, 1057],
- [-122, 1124],
- [-107, 1176],
- [-226, 1223],
- [-257, 1193],
- [-338, 1204],
- [-354, 1228],
- [-457, 1228],
- [-463, 1252],
- [-423, 1288],
- [-441, 1327],
- [-534, 1320],
- [-566, 1304],
- [-596, 1288],
- [-587, 1350],
- [-515, 1391],
- [-569, 1392],
- [-513, 1429],
- [-557, 1437],
- [-523, 1496],
- [-578, 1477],
- [-586, 1499],
- [-540, 1545],
- [-582, 1564],
- [-574, 1593],
- [-606, 1604],
- [-593, 1640],
- [-553, 1673],
- [-514, 1671],
- [-482, 1695],
- [-455, 1674],
- [-372, 1686],
- [-356, 1731],
- [-254, 1763],
- [-154, 1714],
- [-134, 1675],
- [75, 1634],
- [238, 1587],
- [284, 1556],
- [443, 1491],
- [623, 1495],
- [629, 1468],
- [712, 1434],
- [786, 1445],
- [860, 1392],
- [909, 1376],
- [984, 1392],
- [989, 1363],
- [1059, 1338],
- [1034, 1294],
- [1135, 1241],
- [1182, 1231],
- [1213, 1182],
- [1270, 1188],
- [1324, 1134],
- [1370, 1139],
- [1390, 1120],
- [1470, 1114],
- [1478, 1158],
- [1615, 1104],
- [1634, 1067],
- [1645, 1016],
- [1691, 1033],
- [1760, 981],
- [1811, 998],
- [1848, 968],
- [1953, 995],
- [2005, 979],
- [2032, 944],
- [1994, 929],
- [1984, 895],
- [2004, 842],
- [1931, 775],
- [1785, 717],
- [1727, 667],
- [1609, 655],
- [1483, 627],
- [1414, 570],
- [1439, 525],
- [1381, 514],
- [1313, 426],
- [1239, 361],
- [1131, 223],
- [1138, 92],
- [1175, 24],
- [1224, -23],
- [1143, -71],
- [1081, -84],
- [1038, -115],
- [959, -245],
- [961, -319],
- [864, -330],
- [834, -311],
- [724, -358],
- [683, -399],
- [650, -471],
- [586, -524],
- [536, -485],
- [484, -484],
- [458, -514],
- [327, -472],
- [280, -482],
- [239, -459],
- [132, -437],
- [41, -430],
- [12, -464],
- [-40, -480],
- [-81, -467],
- [-154, -480],
- [-207, -551],
- [-212, -550],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'IT',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.44,
- 'hc-middle-y': 0.38,
- 'hc-key': 'it',
- 'hc-a2': 'IT',
- name: 'Italy',
- labelrank: '2',
- 'country-abbrev': 'Italy',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'ITA',
- 'iso-a2': 'IT',
- 'woe-id': '23424853',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [3951, -735],
- [3968, -746],
- [3964, -760],
- [3941, -747],
- [3951, -735],
- ],
- ],
- [
- [
- [4632, -225],
- [4627, -220],
- [4615, -212],
- [4628, -199],
- [4632, -225],
- ],
- ],
- [[[3101, -59], [3124, -77], [3111, -105], [3098, -75], [3101, -59]]],
- [[[3088, -62], [3076, -61], [3070, -47], [3089, -36], [3088, -62]]],
- [[[4361, 430], [4336, 430], [4339, 444], [4362, 437], [4361, 430]]],
- [[[3108, 525], [3108, 513], [3089, 508], [3096, 526], [3108, 525]]],
- [
- [
- [3570, 1021],
- [3559, 994],
- [3500, 1004],
- [3511, 1022],
- [3570, 1021],
- ],
- ],
- [
- [
- [4232, 1848],
- [4234, 1880],
- [4197, 1904],
- [4172, 1883],
- [4130, 1896],
- [4104, 1856],
- [3969, 1791],
- [3987, 1831],
- [3941, 1798],
- [3929, 1753],
- [3956, 1693],
- [3999, 1657],
- [3982, 1613],
- [3951, 1618],
- [3952, 1515],
- [3972, 1447],
- [4046, 1372],
- [4097, 1349],
- [4164, 1295],
- [4243, 1249],
- [4295, 1134],
- [4324, 1039],
- [4355, 983],
- [4508, 842],
- [4595, 804],
- [4666, 801],
- [4791, 822],
- [4831, 780],
- [4776, 731],
- [4777, 691],
- [4803, 671],
- [4934, 618],
- [5040, 591],
- [5140, 527],
- [5263, 487],
- [5321, 437],
- [5395, 344],
- [5373, 247],
- [5302, 273],
- [5278, 332],
- [5239, 378],
- [5164, 374],
- [5094, 397],
- [5054, 427],
- [5026, 402],
- [4967, 290],
- [4976, 256],
- [4949, 206],
- [4957, 172],
- [5012, 157],
- [5111, 103],
- [5111, 19],
- [5133, -6],
- [5113, -47],
- [5052, -47],
- [4988, -111],
- [5003, -197],
- [4918, -289],
- [4904, -348],
- [4805, -351],
- [4795, -270],
- [4825, -257],
- [4848, -197],
- [4830, -143],
- [4905, -111],
- [4910, -66],
- [4874, -17],
- [4845, 89],
- [4813, 123],
- [4787, 211],
- [4743, 268],
- [4685, 243],
- [4632, 288],
- [4582, 305],
- [4599, 352],
- [4538, 431],
- [4446, 398],
- [4475, 442],
- [4433, 472],
- [4389, 467],
- [4300, 585],
- [4268, 571],
- [4211, 594],
- [4151, 571],
- [4115, 620],
- [4058, 632],
- [4015, 684],
- [3968, 713],
- [3946, 762],
- [3876, 798],
- [3837, 869],
- [3780, 903],
- [3714, 902],
- [3734, 937],
- [3690, 992],
- [3634, 1019],
- [3644, 1045],
- [3589, 1053],
- [3594, 1118],
- [3570, 1188],
- [3547, 1214],
- [3533, 1311],
- [3500, 1371],
- [3440, 1384],
- [3317, 1469],
- [3212, 1491],
- [3157, 1458],
- [3107, 1400],
- [3093, 1358],
- [3048, 1329],
- [2951, 1315],
- [2947, 1336],
- [2994, 1398],
- [2987, 1425],
- [2929, 1412],
- [2858, 1449],
- [2825, 1488],
- [2823, 1539],
- [2868, 1577],
- [2845, 1630],
- [2803, 1651],
- [2780, 1703],
- [2830, 1708],
- [2885, 1743],
- [2898, 1787],
- [2863, 1817],
- [2860, 1855],
- [2827, 1879],
- [2830, 1910],
- [2876, 1937],
- [2908, 1923],
- [2981, 1950],
- [3039, 1928],
- [3097, 1988],
- [3091, 2025],
- [3166, 2075],
- [3162, 2022],
- [3196, 1984],
- [3243, 1967],
- [3255, 1898],
- [3287, 1913],
- [3272, 1939],
- [3331, 2036],
- [3330, 2087],
- [3365, 2089],
- [3389, 2031],
- [3470, 2049],
- [3486, 2010],
- [3509, 2022],
- [3486, 2071],
- [3495, 2121],
- [3562, 2101],
- [3551, 2141],
- [3567, 2197],
- [3676, 2170],
- [3694, 2212],
- [3737, 2234],
- [3823, 2232],
- [3913, 2265],
- [3895, 2240],
- [3956, 2154],
- [4122, 2120],
- [4216, 2116],
- [4152, 2046],
- [4209, 2017],
- [4176, 1965],
- [4209, 1955],
- [4203, 1909],
- [4269, 1861],
- [4232, 1848],
- ],
- [
- [3995, 1344],
- [3998, 1370],
- [3986, 1365],
- [3979, 1352],
- [3995, 1344],
- ],
- [[4013, 764], [4013, 764], [4013, 764], [4013, 764], [4013, 764]],
- ],
- [
- [
- [4478, -359],
- [4561, -334],
- [4623, -299],
- [4666, -313],
- [4761, -256],
- [4763, -319],
- [4702, -424],
- [4681, -501],
- [4688, -552],
- [4748, -640],
- [4712, -662],
- [4694, -749],
- [4551, -718],
- [4496, -647],
- [4450, -628],
- [4404, -635],
- [4296, -582],
- [4228, -529],
- [4153, -506],
- [4105, -511],
- [4048, -440],
- [4064, -380],
- [4122, -328],
- [4157, -371],
- [4202, -322],
- [4252, -310],
- [4270, -344],
- [4366, -378],
- [4418, -353],
- [4478, -359],
- ],
- ],
- [
- [
- [3266, -34],
- [3251, -99],
- [3213, -132],
- [3155, -125],
- [3146, -78],
- [3104, -26],
- [3106, 18],
- [3148, 164],
- [3115, 173],
- [3137, 231],
- [3132, 293],
- [3100, 371],
- [3066, 368],
- [3061, 416],
- [3084, 469],
- [3148, 443],
- [3203, 467],
- [3307, 568],
- [3353, 545],
- [3381, 525],
- [3369, 495],
- [3439, 349],
- [3393, 274],
- [3417, 221],
- [3396, 45],
- [3376, -56],
- [3315, -32],
- [3266, -34],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'SM',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.68,
- 'hc-middle-y': 0.41,
- 'hc-key': 'sm',
- 'hc-a2': 'SM',
- name: 'San Marino',
- labelrank: '6',
- 'country-abbrev': 'S.M.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'SMR',
- 'iso-a2': 'SM',
- 'woe-id': '23424947',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [3995, 1344],
- [3979, 1352],
- [3986, 1365],
- [3998, 1370],
- [3995, 1344],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'VA',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.62,
- 'hc-middle-y': 0.44,
- 'hc-key': 'va',
- 'hc-a2': 'VA',
- name: 'Vatican',
- labelrank: '6',
- 'country-abbrev': 'Vat.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'VAT',
- 'iso-a2': 'VA',
- 'woe-id': '23424986',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [[4013, 764], [4013, 764], [4013, 764], [4013, 764], [4013, 764]],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'TR',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.62,
- 'hc-middle-y': 0.51,
- 'hc-key': 'tr',
- 'hc-a2': 'TR',
- name: 'Turkey',
- labelrank: '2',
- 'country-abbrev': 'Tur.',
- subregion: 'Western Asia',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'TUR',
- 'iso-a2': 'TR',
- 'woe-id': '23424969',
- continent: 'Asia',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [[[7048, 641], [7008, 614], [6984, 617], [7031, 663], [7048, 641]]],
- [[[7377, 872], [7400, 872], [7378, 846], [7356, 860], [7377, 872]]],
- [
- [
- [7026, 1104],
- [7060, 1145],
- [7058, 1175],
- [7136, 1215],
- [7149, 1241],
- [7200, 1259],
- [7271, 1219],
- [7320, 1259],
- [7366, 1265],
- [7367, 1220],
- [7404, 1176],
- [7482, 1136],
- [7608, 1115],
- [7653, 1117],
- [7644, 1044],
- [7562, 1015],
- [7478, 1025],
- [7426, 979],
- [7331, 959],
- [7321, 909],
- [7281, 839],
- [7186, 763],
- [7178, 732],
- [7097, 621],
- [7110, 666],
- [7091, 700],
- [7208, 812],
- [7195, 827],
- [7137, 795],
- [7043, 777],
- [7028, 811],
- [7081, 892],
- [7056, 973],
- [7113, 1024],
- [7089, 1088],
- [7026, 1104],
- ],
- ],
- [
- [
- [9732, 429],
- [9709, 427],
- [9717, 324],
- [9732, 308],
- [9732, 245],
- [9709, 236],
- [9731, 170],
- [9699, 144],
- [9700, 102],
- [9638, 110],
- [9642, 143],
- [9568, 212],
- [9628, 325],
- [9607, 380],
- [9556, 400],
- [9498, 327],
- [9502, 279],
- [9445, 237],
- [9287, 266],
- [9243, 235],
- [9170, 98],
- [9163, 32],
- [9133, 54],
- [9107, -9],
- [8982, -55],
- [8915, -110],
- [8801, -100],
- [8689, -21],
- [8509, 5],
- [8352, -16],
- [8335, -55],
- [8356, -128],
- [8340, -219],
- [8301, -212],
- [8181, -304],
- [8158, -284],
- [8099, -292],
- [8040, -273],
- [8000, -223],
- [8004, -185],
- [7961, -170],
- [7948, -209],
- [7892, -205],
- [7879, -172],
- [7800, -182],
- [7811, -212],
- [7781, -263],
- [7741, -246],
- [7754, -221],
- [7682, -240],
- [7674, -274],
- [7600, -284],
- [7653, -235],
- [7752, -207],
- [7737, -175],
- [7792, -123],
- [7626, -189],
- [7594, -180],
- [7558, -209],
- [7560, -159],
- [7597, -159],
- [7617, -99],
- [7583, -114],
- [7568, -76],
- [7518, -98],
- [7500, -29],
- [7450, -20],
- [7499, 16],
- [7480, 85],
- [7391, 80],
- [7355, 125],
- [7323, 84],
- [7232, 113],
- [7280, 153],
- [7234, 219],
- [7268, 237],
- [7305, 205],
- [7329, 146],
- [7417, 196],
- [7380, 197],
- [7311, 268],
- [7348, 280],
- [7369, 348],
- [7329, 331],
- [7324, 377],
- [7249, 421],
- [7305, 498],
- [7291, 525],
- [7117, 451],
- [7123, 498],
- [7103, 589],
- [7133, 619],
- [7143, 674],
- [7201, 751],
- [7262, 762],
- [7321, 796],
- [7363, 765],
- [7436, 783],
- [7400, 829],
- [7479, 841],
- [7456, 812],
- [7644, 862],
- [7687, 862],
- [7724, 893],
- [7635, 896],
- [7672, 943],
- [7792, 990],
- [7866, 1032],
- [7743, 998],
- [7655, 1041],
- [7651, 1092],
- [7679, 1126],
- [7825, 1139],
- [7879, 1153],
- [7902, 1180],
- [8032, 1178],
- [8116, 1207],
- [8142, 1249],
- [8133, 1281],
- [8262, 1408],
- [8278, 1451],
- [8340, 1507],
- [8402, 1540],
- [8471, 1606],
- [8686, 1658],
- [8761, 1684],
- [8797, 1744],
- [8844, 1737],
- [8842, 1713],
- [8888, 1673],
- [8964, 1661],
- [9041, 1721],
- [9086, 1704],
- [9106, 1660],
- [9194, 1625],
- [9215, 1675],
- [9298, 1682],
- [9322, 1656],
- [9449, 1661],
- [9460, 1698],
- [9520, 1672],
- [9616, 1692],
- [9691, 1735],
- [9724, 1777],
- [9732, 1781],
- [9732, 429],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'MT',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.67,
- 'hc-middle-y': 0.77,
- 'hc-key': 'mt',
- 'hc-a2': 'MT',
- name: 'Malta',
- labelrank: '5',
- 'country-abbrev': 'Malta',
- subregion: 'Southern Europe',
- 'region-wb': 'Middle East & North Africa',
- 'iso-a3': 'MLT',
- 'iso-a2': 'MT',
- 'woe-id': '23424897',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [4588, -999],
- [4533, -992],
- [4526, -965],
- [4572, -975],
- [4588, -999],
- ],
- ],
- [
- [
- [4514, -952],
- [4492, -948],
- [4508, -935],
- [4528, -946],
- [4514, -952],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'FR',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.47,
- 'hc-middle-y': 0.47,
- 'hc-key': 'fr',
- 'hc-a2': 'FR',
- name: 'France',
- labelrank: '2',
- 'country-abbrev': 'Fr.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'FRA',
- 'iso-a2': 'FR',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [1209, 2125],
- [1227, 2092],
- [1216, 2069],
- [1190, 2121],
- [1209, 2125],
- ],
- ],
- [
- [
- [1184, 2202],
- [1214, 2184],
- [1223, 2170],
- [1167, 2205],
- [1184, 2202],
- ],
- ],
- [
- [
- [1077, 2433],
- [1065, 2431],
- [1059, 2452],
- [1077, 2447],
- [1077, 2433],
- ],
- ],
- [[[915, 2566], [923, 2556], [890, 2567], [893, 2589], [915, 2566]]],
- [
- [
- [2720, 2051],
- [2736, 2073],
- [2835, 2080],
- [2844, 1995],
- [2876, 1937],
- [2830, 1910],
- [2827, 1879],
- [2860, 1855],
- [2863, 1817],
- [2898, 1787],
- [2885, 1743],
- [2830, 1708],
- [2780, 1703],
- [2803, 1651],
- [2845, 1630],
- [2868, 1577],
- [2823, 1539],
- [2825, 1488],
- [2858, 1449],
- [2929, 1412],
- [2987, 1425],
- [2994, 1398],
- [2947, 1336],
- [2951, 1315],
- [2945, 1307],
- [2937, 1301],
- [2931, 1307],
- [2922, 1295],
- [2876, 1277],
- [2872, 1248],
- [2784, 1209],
- [2754, 1175],
- [2770, 1150],
- [2592, 1113],
- [2547, 1158],
- [2487, 1170],
- [2478, 1211],
- [2429, 1206],
- [2454, 1234],
- [2402, 1241],
- [2378, 1215],
- [2324, 1226],
- [2315, 1252],
- [2250, 1258],
- [2231, 1287],
- [2195, 1284],
- [2072, 1222],
- [2010, 1176],
- [1989, 1130],
- [1981, 1017],
- [2005, 979],
- [1953, 995],
- [1848, 968],
- [1811, 998],
- [1760, 981],
- [1691, 1033],
- [1697, 1064],
- [1634, 1067],
- [1615, 1104],
- [1478, 1158],
- [1470, 1114],
- [1390, 1120],
- [1370, 1139],
- [1324, 1134],
- [1270, 1188],
- [1213, 1182],
- [1182, 1231],
- [1135, 1241],
- [1034, 1294],
- [1059, 1338],
- [989, 1363],
- [984, 1392],
- [1013, 1392],
- [1059, 1437],
- [1169, 1739],
- [1152, 1731],
- [1216, 1974],
- [1230, 1995],
- [1284, 1915],
- [1291, 1943],
- [1214, 2039],
- [1247, 2091],
- [1256, 2193],
- [1130, 2285],
- [1126, 2324],
- [1083, 2386],
- [1119, 2448],
- [1091, 2486],
- [1101, 2527],
- [1073, 2513],
- [1026, 2561],
- [1060, 2567],
- [1044, 2600],
- [981, 2603],
- [996, 2638],
- [936, 2634],
- [860, 2708],
- [799, 2726],
- [788, 2753],
- [700, 2747],
- [698, 2797],
- [656, 2833],
- [733, 2829],
- [736, 2855],
- [696, 2895],
- [737, 2918],
- [659, 2913],
- [664, 2956],
- [711, 2987],
- [760, 2981],
- [828, 2994],
- [845, 2960],
- [858, 2988],
- [901, 2967],
- [920, 3008],
- [1000, 3002],
- [1059, 2885],
- [1137, 2923],
- [1151, 2890],
- [1227, 2916],
- [1224, 2890],
- [1313, 2887],
- [1283, 2917],
- [1297, 2997],
- [1296, 3054],
- [1264, 3110],
- [1258, 3206],
- [1317, 3176],
- [1357, 3187],
- [1384, 3157],
- [1366, 3140],
- [1383, 3100],
- [1447, 3071],
- [1523, 3053],
- [1556, 3030],
- [1633, 3059],
- [1681, 3063],
- [1625, 3083],
- [1659, 3147],
- [1746, 3178],
- [1841, 3190],
- [1906, 3248],
- [1919, 3273],
- [1946, 3441],
- [2022, 3473],
- [2124, 3485],
- [2123, 3485],
- [2129, 3413],
- [2161, 3376],
- [2227, 3382],
- [2244, 3311],
- [2310, 3281],
- [2310, 3245],
- [2377, 3244],
- [2408, 3217],
- [2390, 3138],
- [2443, 3122],
- [2486, 3135],
- [2492, 3162],
- [2529, 3175],
- [2510, 3129],
- [2519, 3074],
- [2536, 3076],
- [2629, 2982],
- [2688, 2991],
- [2719, 2961],
- [2752, 2975],
- [2791, 2962],
- [2819, 2954],
- [2854, 2877],
- [2879, 2886],
- [2960, 2853],
- [2984, 2871],
- [3032, 2830],
- [3085, 2829],
- [3136, 2805],
- [3116, 2766],
- [3059, 2708],
- [3045, 2634],
- [3008, 2560],
- [3016, 2533],
- [2996, 2470],
- [3006, 2412],
- [2964, 2369],
- [2892, 2392],
- [2862, 2351],
- [2897, 2346],
- [2805, 2247],
- [2773, 2237],
- [2768, 2184],
- [2706, 2139],
- [2688, 2089],
- [2704, 2071],
- [2664, 2007],
- [2731, 2035],
- [2720, 2051],
- ],
- [
- [1743, 1010],
- [1751, 1009],
- [1746, 1022],
- [1741, 1016],
- [1743, 1010],
- ],
- ],
- [
- [
- [3204, 798],
- [3163, 857],
- [3201, 949],
- [3285, 996],
- [3325, 981],
- [3337, 1073],
- [3354, 1080],
- [3367, 1026],
- [3360, 972],
- [3377, 942],
- [3380, 816],
- [3347, 770],
- [3346, 698],
- [3306, 600],
- [3212, 657],
- [3241, 695],
- [3192, 706],
- [3203, 766],
- [3169, 777],
- [3204, 798],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'NO',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.43,
- 'hc-middle-y': 0.8,
- 'hc-key': 'no',
- 'hc-a2': 'NO',
- name: 'Norway',
- labelrank: '3',
- 'country-abbrev': 'Nor.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'NOR',
- 'iso-a2': 'NO',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [2823, 5756],
- [2848, 5748],
- [2843, 5737],
- [2818, 5753],
- [2823, 5756],
- ],
- ],
- [
- [
- [2800, 5766],
- [2790, 5779],
- [2796, 5789],
- [2803, 5778],
- [2800, 5766],
- ],
- ],
- [
- [
- [2884, 5781],
- [2873, 5785],
- [2877, 5796],
- [2899, 5793],
- [2884, 5781],
- ],
- ],
- [
- [
- [2777, 5805],
- [2777, 5769],
- [2759, 5778],
- [2769, 5841],
- [2777, 5805],
- ],
- ],
- [
- [
- [2778, 5961],
- [2792, 5938],
- [2757, 5944],
- [2763, 5974],
- [2778, 5961],
- ],
- ],
- [
- [
- [2819, 5951],
- [2791, 5950],
- [2785, 5984],
- [2810, 5991],
- [2819, 5951],
- ],
- ],
- [
- [
- [2847, 6026],
- [2842, 5982],
- [2822, 5979],
- [2818, 6024],
- [2847, 6026],
- ],
- ],
- [
- [
- [2789, 6032],
- [2789, 6009],
- [2776, 6024],
- [2782, 6042],
- [2789, 6032],
- ],
- ],
- [
- [
- [2770, 6105],
- [2770, 6070],
- [2748, 6098],
- [2755, 6142],
- [2770, 6105],
- ],
- ],
- [
- [
- [2764, 6164],
- [2782, 6157],
- [2786, 6132],
- [2759, 6160],
- [2764, 6164],
- ],
- ],
- [
- [
- [2744, 6285],
- [2757, 6268],
- [2750, 6265],
- [2737, 6285],
- [2744, 6285],
- ],
- ],
- [
- [
- [2734, 6314],
- [2729, 6303],
- [2722, 6324],
- [2730, 6330],
- [2734, 6314],
- ],
- ],
- [
- [
- [2758, 6346],
- [2771, 6338],
- [2743, 6323],
- [2745, 6350],
- [2758, 6346],
- ],
- ],
- [
- [
- [2782, 6565],
- [2805, 6551],
- [2775, 6531],
- [2760, 6550],
- [2782, 6565],
- ],
- ],
- [
- [
- [2800, 6596],
- [2811, 6592],
- [2802, 6572],
- [2789, 6604],
- [2800, 6596],
- ],
- ],
- [
- [
- [2893, 6675],
- [2901, 6648],
- [2869, 6646],
- [2873, 6674],
- [2893, 6675],
- ],
- ],
- [
- [
- [2880, 6686],
- [2899, 6693],
- [2896, 6683],
- [2868, 6687],
- [2880, 6686],
- ],
- ],
- [
- [
- [2943, 6687],
- [2915, 6661],
- [2907, 6693],
- [2927, 6711],
- [2943, 6687],
- ],
- ],
- [
- [
- [2964, 6713],
- [2978, 6701],
- [2947, 6703],
- [2938, 6713],
- [2964, 6713],
- ],
- ],
- [
- [
- [3061, 6794],
- [3062, 6784],
- [3029, 6773],
- [3032, 6794],
- [3061, 6794],
- ],
- ],
- [
- [
- [3050, 6830],
- [3065, 6830],
- [3059, 6806],
- [3046, 6817],
- [3050, 6830],
- ],
- ],
- [
- [
- [3200, 6891],
- [3183, 6874],
- [3169, 6884],
- [3176, 6896],
- [3200, 6891],
- ],
- ],
- [
- [
- [3168, 6874],
- [3147, 6853],
- [3131, 6886],
- [3153, 6875],
- [3168, 6874],
- ],
- ],
- [
- [
- [3253, 6915],
- [3238, 6915],
- [3235, 6931],
- [3244, 6931],
- [3253, 6915],
- ],
- ],
- [
- [
- [3237, 6913],
- [3214, 6907],
- [3203, 6920],
- [3223, 6938],
- [3237, 6913],
- ],
- ],
- [
- [
- [3227, 6967],
- [3188, 6977],
- [3215, 7006],
- [3238, 6981],
- [3227, 6967],
- ],
- ],
- [
- [
- [3321, 7101],
- [3324, 7076],
- [3313, 7067],
- [3257, 7062],
- [3321, 7101],
- ],
- ],
- [
- [
- [3651, 7346],
- [3663, 7333],
- [3660, 7319],
- [3649, 7325],
- [3651, 7346],
- ],
- ],
- [
- [
- [3633, 7339],
- [3658, 7299],
- [3628, 7308],
- [3615, 7351],
- [3633, 7339],
- ],
- ],
- [
- [
- [3648, 7351],
- [3644, 7336],
- [3627, 7357],
- [3642, 7363],
- [3648, 7351],
- ],
- ],
- [
- [
- [3629, 7427],
- [3613, 7424],
- [3581, 7411],
- [3614, 7439],
- [3629, 7427],
- ],
- ],
- [
- [
- [3580, 7414],
- [3570, 7409],
- [3576, 7431],
- [3617, 7446],
- [3580, 7414],
- ],
- ],
- [
- [
- [3699, 7488],
- [3672, 7467],
- [3667, 7471],
- [3675, 7487],
- [3699, 7488],
- ],
- ],
- [
- [
- [3759, 7501],
- [3747, 7473],
- [3727, 7478],
- [3750, 7520],
- [3759, 7501],
- ],
- ],
- [
- [
- [3773, 7608],
- [3785, 7573],
- [3736, 7520],
- [3740, 7562],
- [3773, 7608],
- ],
- ],
- [
- [
- [3716, 7662],
- [3713, 7631],
- [3693, 7638],
- [3701, 7661],
- [3716, 7662],
- ],
- ],
- [
- [
- [3809, 7748],
- [3780, 7719],
- [3765, 7722],
- [3777, 7749],
- [3809, 7748],
- ],
- ],
- [
- [
- [3791, 7809],
- [3797, 7789],
- [3783, 7773],
- [3755, 7758],
- [3791, 7809],
- ],
- ],
- [
- [
- [3814, 7846],
- [3823, 7829],
- [3809, 7822],
- [3803, 7832],
- [3814, 7846],
- ],
- ],
- [
- [
- [3830, 7885],
- [3824, 7862],
- [3812, 7870],
- [3813, 7887],
- [3830, 7885],
- ],
- ],
- [
- [
- [3971, 8096],
- [3969, 8064],
- [3943, 8083],
- [3950, 8101],
- [3971, 8096],
- ],
- ],
- [
- [
- [3984, 8190],
- [3983, 8182],
- [3965, 8175],
- [3980, 8195],
- [3984, 8190],
- ],
- ],
- [
- [
- [4054, 8360],
- [4075, 8347],
- [4039, 8341],
- [4039, 8358],
- [4054, 8360],
- ],
- ],
- [
- [
- [4072, 8359],
- [4062, 8358],
- [4059, 8367],
- [4066, 8380],
- [4072, 8359],
- ],
- ],
- [
- [
- [3826, 8372],
- [3831, 8341],
- [3794, 8296],
- [3813, 8365],
- [3826, 8372],
- ],
- ],
- [
- [
- [3859, 8374],
- [3843, 8351],
- [3835, 8373],
- [3863, 8394],
- [3859, 8374],
- ],
- ],
- [
- [
- [4024, 8439],
- [4013, 8439],
- [4022, 8456],
- [4030, 8450],
- [4024, 8439],
- ],
- ],
- [
- [
- [3950, 8456],
- [3946, 8436],
- [3940, 8449],
- [3930, 8460],
- [3950, 8456],
- ],
- ],
- [
- [
- [3920, 8440],
- [3929, 8414],
- [3900, 8406],
- [3887, 8379],
- [3868, 8396],
- [3868, 8430],
- [3920, 8440],
- ],
- ],
- [
- [
- [4049, 8500],
- [4009, 8443],
- [3948, 8407],
- [3978, 8458],
- [3965, 8474],
- [3993, 8493],
- [4049, 8500],
- ],
- ],
- [
- [
- [4187, 8508],
- [4154, 8495],
- [4161, 8532],
- [4178, 8540],
- [4187, 8508],
- ],
- ],
- [
- [
- [4004, 8514],
- [3985, 8532],
- [4029, 8533],
- [4018, 8515],
- [4004, 8514],
- ],
- ],
- [
- [
- [4255, 8614],
- [4227, 8601],
- [4226, 8616],
- [4242, 8644],
- [4255, 8614],
- ],
- ],
- [
- [
- [4265, 8646],
- [4263, 8664],
- [4288, 8650],
- [4267, 8626],
- [4265, 8646],
- ],
- ],
- [
- [
- [4018, 8630],
- [4005, 8632],
- [4008, 8645],
- [4017, 8645],
- [4018, 8630],
- ],
- ],
- [
- [
- [4174, 8672],
- [4190, 8672],
- [4192, 8647],
- [4157, 8669],
- [4174, 8672],
- ],
- ],
- [
- [
- [4280, 8687],
- [4273, 8700],
- [4282, 8716],
- [4293, 8708],
- [4280, 8687],
- ],
- ],
- [
- [
- [4108, 8681],
- [4067, 8660],
- [4079, 8704],
- [4131, 8772],
- [4131, 8720],
- [4108, 8681],
- ],
- ],
- [
- [
- [4402, 8972],
- [4427, 8934],
- [4405, 8912],
- [4411, 8885],
- [4347, 8859],
- [4324, 8875],
- [4355, 8879],
- [4340, 8913],
- [4355, 8940],
- [4400, 8943],
- [4402, 8972],
- ],
- ],
- [
- [
- [4594, 8976],
- [4583, 8973],
- [4580, 9002],
- [4595, 9008],
- [4594, 8976],
- ],
- ],
- [
- [
- [5553, 9194],
- [5561, 9190],
- [5528, 9163],
- [5532, 9212],
- [5553, 9194],
- ],
- ],
- [
- [
- [4490, 8977],
- [4471, 8969],
- [4472, 8994],
- [4504, 9015],
- [4490, 8977],
- ],
- ],
- [
- [
- [4616, 9050],
- [4630, 9045],
- [4617, 9023],
- [4596, 9036],
- [4616, 9050],
- ],
- ],
- [
- [
- [4434, 9036],
- [4474, 9017],
- [4462, 8968],
- [4435, 8949],
- [4388, 8992],
- [4397, 9014],
- [4434, 9036],
- ],
- ],
- [
- [
- [4391, 9028],
- [4409, 9033],
- [4384, 9003],
- [4379, 9041],
- [4391, 9028],
- ],
- ],
- [
- [
- [4606, 9062],
- [4599, 9059],
- [4596, 9083],
- [4607, 9071],
- [4606, 9062],
- ],
- ],
- [
- [
- [4467, 9047],
- [4447, 9050],
- [4446, 9063],
- [4454, 9064],
- [4467, 9047],
- ],
- ],
- [
- [
- [4592, 9071],
- [4576, 9048],
- [4553, 9062],
- [4568, 9105],
- [4594, 9096],
- [4592, 9071],
- ],
- ],
- [
- [
- [4425, 9091],
- [4432, 9055],
- [4420, 9048],
- [4408, 9057],
- [4425, 9091],
- ],
- ],
- [
- [
- [4476, 9096],
- [4491, 9074],
- [4528, 9061],
- [4496, 9041],
- [4471, 9086],
- [4476, 9096],
- ],
- ],
- [
- [
- [4683, 9132],
- [4676, 9137],
- [4675, 9169],
- [4684, 9161],
- [4683, 9132],
- ],
- ],
- [
- [
- [4774, 9185],
- [4804, 9172],
- [4813, 9150],
- [4754, 9152],
- [4753, 9181],
- [4774, 9185],
- ],
- ],
- [
- [
- [4843, 9269],
- [4867, 9228],
- [4848, 9178],
- [4821, 9162],
- [4791, 9208],
- [4843, 9269],
- ],
- ],
- [
- [
- [4869, 9303],
- [4905, 9283],
- [4873, 9246],
- [4852, 9298],
- [4869, 9303],
- ],
- ],
- [
- [
- [4876, 9399],
- [4883, 9370],
- [4863, 9379],
- [4864, 9402],
- [4876, 9399],
- ],
- ],
- [
- [
- [4940, 9438],
- [4944, 9418],
- [4926, 9424],
- [4933, 9441],
- [4940, 9438],
- ],
- ],
- [
- [
- [5023, 9477],
- [5051, 9449],
- [5083, 9441],
- [5024, 9405],
- [4993, 9434],
- [5023, 9477],
- ],
- ],
- [
- [
- [1712, 9534],
- [1697, 9493],
- [1622, 9482],
- [1587, 9453],
- [1577, 9474],
- [1647, 9502],
- [1675, 9535],
- [1712, 9534],
- ],
- ],
- [
- [
- [2837, 6137],
- [2847, 6128],
- [2861, 6143],
- [2864, 6225],
- [2838, 6190],
- [2804, 6173],
- [2756, 6246],
- [2808, 6216],
- [2808, 6249],
- [2774, 6257],
- [2774, 6317],
- [2832, 6307],
- [2898, 6331],
- [2951, 6306],
- [2999, 6341],
- [3049, 6311],
- [3103, 6327],
- [3106, 6341],
- [3048, 6320],
- [2986, 6367],
- [2975, 6324],
- [2939, 6346],
- [2852, 6338],
- [2825, 6323],
- [2809, 6354],
- [2778, 6366],
- [2772, 6425],
- [2831, 6461],
- [2782, 6487],
- [2782, 6518],
- [2834, 6545],
- [2839, 6563],
- [2889, 6540],
- [2997, 6528],
- [2848, 6572],
- [2810, 6563],
- [2834, 6599],
- [2804, 6641],
- [2820, 6655],
- [2854, 6608],
- [2853, 6645],
- [2921, 6644],
- [2937, 6675],
- [3031, 6713],
- [2977, 6718],
- [3009, 6739],
- [2972, 6738],
- [2981, 6761],
- [3020, 6745],
- [3043, 6771],
- [3086, 6769],
- [3142, 6735],
- [3144, 6774],
- [3186, 6789],
- [3226, 6784],
- [3219, 6802],
- [3125, 6778],
- [3116, 6795],
- [3070, 6791],
- [3065, 6852],
- [3114, 6874],
- [3143, 6843],
- [3207, 6860],
- [3223, 6899],
- [3256, 6854],
- [3254, 6831],
- [3293, 6829],
- [3234, 6900],
- [3284, 6904],
- [3282, 6916],
- [3268, 6913],
- [3248, 6934],
- [3277, 6938],
- [3279, 6933],
- [3276, 6952],
- [3314, 6967],
- [3323, 6991],
- [3380, 6970],
- [3367, 7013],
- [3438, 7053],
- [3472, 6989],
- [3455, 6960],
- [3491, 6959],
- [3490, 6992],
- [3517, 7000],
- [3583, 6986],
- [3598, 7001],
- [3566, 7025],
- [3612, 7076],
- [3668, 7101],
- [3620, 7126],
- [3671, 7165],
- [3640, 7169],
- [3602, 7143],
- [3617, 7110],
- [3596, 7079],
- [3532, 7034],
- [3467, 7015],
- [3451, 7067],
- [3481, 7094],
- [3418, 7059],
- [3418, 7091],
- [3503, 7139],
- [3469, 7145],
- [3499, 7216],
- [3555, 7265],
- [3555, 7291],
- [3591, 7298],
- [3599, 7336],
- [3626, 7290],
- [3653, 7276],
- [3670, 7328],
- [3654, 7368],
- [3710, 7397],
- [3684, 7402],
- [3717, 7441],
- [3664, 7398],
- [3633, 7401],
- [3653, 7431],
- [3707, 7452],
- [3725, 7476],
- [3747, 7468],
- [3802, 7496],
- [3840, 7559],
- [3789, 7501],
- [3768, 7537],
- [3803, 7582],
- [3793, 7633],
- [3773, 7616],
- [3765, 7646],
- [3815, 7745],
- [3843, 7777],
- [3805, 7768],
- [3823, 7796],
- [3901, 7831],
- [3954, 7833],
- [3906, 7846],
- [3842, 7816],
- [3875, 7848],
- [3836, 7863],
- [3856, 7880],
- [3862, 7946],
- [3889, 7960],
- [3894, 7999],
- [3925, 8002],
- [3898, 8039],
- [3971, 8056],
- [3984, 8110],
- [4032, 8130],
- [4047, 8109],
- [4068, 8132],
- [4100, 8120],
- [4110, 8135],
- [4064, 8142],
- [4074, 8162],
- [4019, 8130],
- [4019, 8148],
- [3979, 8135],
- [4013, 8192],
- [4034, 8179],
- [4058, 8201],
- [4020, 8211],
- [4051, 8236],
- [4106, 8209],
- [4149, 8246],
- [4108, 8224],
- [4081, 8226],
- [4071, 8262],
- [4114, 8296],
- [4088, 8311],
- [4030, 8258],
- [4023, 8284],
- [4048, 8291],
- [4021, 8312],
- [4043, 8335],
- [4077, 8336],
- [4131, 8359],
- [4149, 8378],
- [4143, 8378],
- [4136, 8369],
- [4120, 8376],
- [4106, 8379],
- [4072, 8380],
- [4095, 8411],
- [4142, 8414],
- [4143, 8449],
- [4160, 8428],
- [4157, 8385],
- [4183, 8343],
- [4187, 8376],
- [4208, 8350],
- [4209, 8385],
- [4190, 8403],
- [4197, 8447],
- [4164, 8472],
- [4214, 8458],
- [4169, 8483],
- [4220, 8504],
- [4236, 8481],
- [4313, 8520],
- [4310, 8551],
- [4258, 8516],
- [4201, 8512],
- [4206, 8574],
- [4269, 8615],
- [4315, 8661],
- [4285, 8660],
- [4307, 8721],
- [4347, 8749],
- [4335, 8783],
- [4360, 8847],
- [4398, 8795],
- [4432, 8809],
- [4379, 8837],
- [4379, 8860],
- [4415, 8874],
- [4438, 8829],
- [4476, 8818],
- [4476, 8781],
- [4494, 8783],
- [4475, 8829],
- [4454, 8825],
- [4426, 8878],
- [4434, 8933],
- [4453, 8950],
- [4503, 8963],
- [4497, 8924],
- [4518, 8910],
- [4514, 8952],
- [4528, 8955],
- [4537, 9010],
- [4565, 8998],
- [4562, 8851],
- [4539, 8804],
- [4572, 8846],
- [4587, 8905],
- [4581, 8963],
- [4644, 9005],
- [4660, 9055],
- [4723, 9003],
- [4718, 9051],
- [4704, 9071],
- [4669, 9076],
- [4632, 9106],
- [4666, 9144],
- [4703, 9099],
- [4692, 9135],
- [4736, 9150],
- [4749, 9138],
- [4812, 9136],
- [4828, 9084],
- [4863, 9066],
- [4880, 9092],
- [4839, 9106],
- [4860, 9124],
- [4842, 9148],
- [4868, 9173],
- [4858, 9194],
- [4891, 9245],
- [4932, 9245],
- [4908, 9261],
- [4923, 9308],
- [4953, 9294],
- [4939, 9329],
- [4913, 9329],
- [4917, 9362],
- [4944, 9340],
- [4936, 9401],
- [4987, 9394],
- [5005, 9357],
- [5018, 9381],
- [5061, 9402],
- [5068, 9391],
- [5007, 9267],
- [5022, 9278],
- [5032, 9211],
- [5015, 9183],
- [5022, 9138],
- [5040, 9142],
- [5067, 9216],
- [5053, 9221],
- [5092, 9324],
- [5092, 9351],
- [5111, 9405],
- [5136, 9438],
- [5151, 9400],
- [5149, 9363],
- [5123, 9338],
- [5155, 9343],
- [5159, 9257],
- [5203, 9350],
- [5195, 9385],
- [5226, 9426],
- [5181, 9438],
- [5201, 9450],
- [5186, 9473],
- [5220, 9459],
- [5211, 9501],
- [5245, 9491],
- [5272, 9513],
- [5278, 9486],
- [5314, 9491],
- [5314, 9454],
- [5298, 9425],
- [5252, 9415],
- [5293, 9408],
- [5255, 9357],
- [5312, 9402],
- [5312, 9374],
- [5284, 9325],
- [5323, 9308],
- [5342, 9231],
- [5329, 9315],
- [5333, 9413],
- [5347, 9465],
- [5372, 9475],
- [5414, 9455],
- [5416, 9427],
- [5455, 9448],
- [5454, 9414],
- [5477, 9450],
- [5582, 9386],
- [5590, 9401],
- [5620, 9356],
- [5571, 9330],
- [5547, 9266],
- [5450, 9255],
- [5399, 9256],
- [5388, 9238],
- [5508, 9223],
- [5489, 9196],
- [5519, 9206],
- [5527, 9154],
- [5560, 9161],
- [5576, 9184],
- [5564, 9210],
- [5601, 9202],
- [5638, 9209],
- [5659, 9172],
- [5664, 9128],
- [5628, 9120],
- [5599, 9138],
- [5597, 9081],
- [5581, 9050],
- [5529, 9010],
- [5532, 8960],
- [5509, 8926],
- [5490, 8946],
- [5485, 8979],
- [5515, 9065],
- [5476, 9121],
- [5392, 9139],
- [5361, 9160],
- [5320, 9205],
- [5290, 9182],
- [5252, 9135],
- [5225, 9140],
- [5190, 9125],
- [5173, 9058],
- [5157, 9048],
- [5153, 8993],
- [5162, 8948],
- [5159, 8892],
- [5178, 8840],
- [5171, 8799],
- [5127, 8767],
- [5129, 8714],
- [5106, 8686],
- [5079, 8712],
- [5041, 8717],
- [4991, 8740],
- [4976, 8706],
- [4923, 8666],
- [4900, 8683],
- [4835, 8679],
- [4823, 8711],
- [4776, 8761],
- [4725, 8830],
- [4687, 8832],
- [4662, 8795],
- [4680, 8769],
- [4669, 8753],
- [4635, 8767],
- [4625, 8745],
- [4570, 8732],
- [4600, 8702],
- [4600, 8626],
- [4575, 8586],
- [4608, 8572],
- [4579, 8529],
- [4475, 8562],
- [4432, 8556],
- [4406, 8575],
- [4379, 8560],
- [4391, 8447],
- [4367, 8387],
- [4298, 8421],
- [4246, 8357],
- [4219, 8248],
- [4188, 8230],
- [4182, 8204],
- [4224, 8141],
- [4225, 8096],
- [4186, 8043],
- [4149, 7954],
- [4123, 7920],
- [4134, 7856],
- [4086, 7816],
- [4024, 7804],
- [4042, 7721],
- [4035, 7678],
- [4036, 7564],
- [4015, 7494],
- [3940, 7341],
- [4002, 7302],
- [4012, 7233],
- [3986, 7174],
- [3894, 7193],
- [3825, 7151],
- [3762, 7043],
- [3767, 7001],
- [3741, 6952],
- [3774, 6868],
- [3759, 6845],
- [3767, 6790],
- [3760, 6746],
- [3794, 6644],
- [3777, 6495],
- [3814, 6454],
- [3841, 6445],
- [3880, 6395],
- [3857, 6297],
- [3797, 6278],
- [3816, 6212],
- [3850, 6144],
- [3836, 6080],
- [3842, 6028],
- [3790, 5963],
- [3748, 5954],
- [3756, 5924],
- [3725, 5862],
- [3747, 5773],
- [3727, 5677],
- [3695, 5669],
- [3693, 5697],
- [3682, 5728],
- [3612, 5755],
- [3600, 5745],
- [3566, 5820],
- [3574, 5870],
- [3560, 5908],
- [3573, 5946],
- [3555, 5954],
- [3549, 5910],
- [3565, 5863],
- [3549, 5847],
- [3547, 5769],
- [3531, 5776],
- [3524, 5734],
- [3471, 5685],
- [3431, 5685],
- [3368, 5649],
- [3395, 5649],
- [3249, 5484],
- [3226, 5481],
- [3190, 5441],
- [3177, 5477],
- [3158, 5436],
- [3106, 5419],
- [3017, 5426],
- [2946, 5455],
- [2973, 5484],
- [2870, 5537],
- [2865, 5560],
- [2827, 5578],
- [2797, 5647],
- [2813, 5729],
- [2839, 5695],
- [2883, 5686],
- [2862, 5740],
- [2921, 5792],
- [2897, 5830],
- [2835, 5805],
- [2825, 5838],
- [2806, 5799],
- [2767, 5869],
- [2816, 5932],
- [2853, 5912],
- [2876, 5935],
- [2841, 5964],
- [2890, 5991],
- [2919, 6065],
- [2984, 6134],
- [2928, 6105],
- [2878, 6052],
- [2875, 6012],
- [2850, 6018],
- [2855, 6053],
- [2837, 6076],
- [2815, 6056],
- [2787, 6072],
- [2784, 6124],
- [2805, 6163],
- [2820, 6150],
- [2808, 6168],
- [2862, 6203],
- [2857, 6144],
- [2837, 6137],
- ],
- ],
- [
- [
- [3351, 7049],
- [3352, 7056],
- [3362, 7056],
- [3368, 7050],
- [3353, 7046],
- [3369, 7031],
- [3280, 6993],
- [3251, 7003],
- [3287, 7042],
- [3342, 7057],
- [3351, 7049],
- ],
- ],
- [
- [
- [4021, 8662],
- [4042, 8649],
- [4054, 8642],
- [4064, 8566],
- [4036, 8541],
- [4007, 8557],
- [4044, 8579],
- [4041, 8595],
- [3986, 8552],
- [3957, 8569],
- [3990, 8595],
- [4025, 8605],
- [4021, 8662],
- ],
- ],
- [
- [
- [4773, 9266],
- [4803, 9297],
- [4786, 9313],
- [4816, 9315],
- [4819, 9336],
- [4816, 9285],
- [4784, 9234],
- [4713, 9205],
- [4726, 9247],
- [4694, 9238],
- [4726, 9274],
- [4773, 9266],
- ],
- ],
- [
- [
- [4199, 8593],
- [4192, 8544],
- [4150, 8536],
- [4140, 8493],
- [4115, 8501],
- [4104, 8462],
- [4073, 8476],
- [4050, 8462],
- [4035, 8461],
- [4061, 8507],
- [4055, 8538],
- [4084, 8562],
- [4073, 8614],
- [4091, 8659],
- [4119, 8663],
- [4119, 8609],
- [4133, 8605],
- [4118, 8557],
- [4146, 8599],
- [4158, 8649],
- [4180, 8635],
- [4199, 8593],
- ],
- ],
- [
- [
- [4220, 8743],
- [4241, 8781],
- [4218, 8805],
- [4266, 8805],
- [4276, 8820],
- [4250, 8833],
- [4254, 8851],
- [4291, 8832],
- [4307, 8874],
- [4333, 8847],
- [4341, 8804],
- [4323, 8792],
- [4334, 8751],
- [4284, 8737],
- [4215, 8700],
- [4238, 8726],
- [4220, 8743],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'DE',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.47,
- 'hc-middle-y': 0.53,
- 'hc-key': 'de',
- 'hc-a2': 'DE',
- name: 'Germany',
- labelrank: '2',
- 'country-abbrev': 'Ger.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'DEU',
- 'iso-a2': 'DE',
- 'woe-id': '23424829',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [3422, 2394],
- [3345, 2433],
- [3297, 2473],
- [3318, 2430],
- [3264, 2425],
- [3205, 2468],
- [3164, 2431],
- [3174, 2408],
- [3006, 2412],
- [2996, 2470],
- [3016, 2533],
- [3008, 2560],
- [3045, 2634],
- [3059, 2708],
- [3116, 2766],
- [3136, 2805],
- [3085, 2829],
- [3032, 2830],
- [2984, 2871],
- [2960, 2853],
- [2879, 2886],
- [2854, 2877],
- [2819, 2954],
- [2791, 2962],
- [2825, 3058],
- [2774, 3087],
- [2759, 3155],
- [2765, 3182],
- [2809, 3209],
- [2804, 3257],
- [2779, 3261],
- [2778, 3297],
- [2746, 3338],
- [2761, 3382],
- [2737, 3419],
- [2780, 3452],
- [2766, 3475],
- [2794, 3519],
- [2779, 3594],
- [2751, 3642],
- [2788, 3666],
- [2832, 3644],
- [2910, 3678],
- [2887, 3701],
- [2952, 3754],
- [2945, 3818],
- [2897, 3827],
- [2912, 3874],
- [2959, 3874],
- [2969, 3933],
- [2988, 3965],
- [2993, 4045],
- [3013, 4070],
- [2965, 4084],
- [2978, 4144],
- [3003, 4166],
- [3128, 4178],
- [3163, 4128],
- [3144, 4114],
- [3169, 4087],
- [3189, 4115],
- [3179, 4143],
- [3231, 4125],
- [3218, 4165],
- [3235, 4214],
- [3283, 4206],
- [3343, 4213],
- [3309, 4223],
- [3280, 4265],
- [3282, 4328],
- [3250, 4334],
- [3247, 4367],
- [3286, 4374],
- [3312, 4400],
- [3259, 4467],
- [3255, 4513],
- [3282, 4516],
- [3384, 4487],
- [3384, 4487],
- [3408, 4503],
- [3483, 4447],
- [3481, 4410],
- [3608, 4345],
- [3659, 4353],
- [3658, 4307],
- [3605, 4266],
- [3628, 4244],
- [3677, 4260],
- [3725, 4229],
- [3731, 4256],
- [3770, 4302],
- [3833, 4308],
- [3872, 4347],
- [3897, 4397],
- [3961, 4368],
- [3987, 4389],
- [4002, 4345],
- [4064, 4296],
- [4103, 4313],
- [4119, 4289],
- [4122, 4320],
- [4193, 4256],
- [4187, 4247],
- [4192, 4239],
- [4137, 4226],
- [4146, 4291],
- [4124, 4280],
- [4141, 4255],
- [4127, 4235],
- [4157, 4207],
- [4206, 4188],
- [4238, 4067],
- [4231, 4002],
- [4198, 3974],
- [4196, 3943],
- [4292, 3870],
- [4276, 3824],
- [4307, 3777],
- [4322, 3727],
- [4295, 3647],
- [4322, 3611],
- [4327, 3566],
- [4367, 3549],
- [4385, 3489],
- [4354, 3378],
- [4318, 3372],
- [4293, 3419],
- [4256, 3423],
- [4269, 3379],
- [4136, 3323],
- [4109, 3288],
- [4070, 3282],
- [4030, 3255],
- [3940, 3221],
- [3900, 3155],
- [3860, 3197],
- [3894, 3121],
- [3947, 3082],
- [3923, 3034],
- [4004, 2918],
- [4043, 2910],
- [4079, 2860],
- [4139, 2811],
- [4163, 2815],
- [4205, 2765],
- [4189, 2693],
- [4151, 2710],
- [4132, 2649],
- [4062, 2613],
- [4008, 2569],
- [4059, 2492],
- [4041, 2456],
- [4066, 2454],
- [4072, 2390],
- [4021, 2406],
- [4016, 2438],
- [3965, 2426],
- [3902, 2446],
- [3900, 2416],
- [3794, 2409],
- [3724, 2353],
- [3670, 2350],
- [3632, 2385],
- [3558, 2401],
- [3566, 2362],
- [3538, 2323],
- [3467, 2387],
- [3422, 2394],
- ],
- [
- [3343, 4213],
- [3357, 4210],
- [3357, 4210],
- [3357, 4210],
- [3357, 4210],
- [3403, 4138],
- [3374, 4205],
- [3357, 4210],
- [3357, 4210],
- [3357, 4210],
- [3357, 4210],
- [3354, 4215],
- [3343, 4213],
- ],
- ],
- [
- [
- [3731, 4262],
- [3727, 4247],
- [3711, 4250],
- [3726, 4265],
- [3731, 4262],
- ],
- ],
- [
- [
- [3291, 4389],
- [3278, 4399],
- [3284, 4404],
- [3304, 4405],
- [3291, 4389],
- ],
- ],
- [
- [
- [3688, 4393],
- [3698, 4374],
- [3646, 4389],
- [3665, 4409],
- [3688, 4393],
- ],
- ],
- [
- [
- [4044, 4454],
- [4086, 4437],
- [4077, 4404],
- [4111, 4369],
- [4066, 4367],
- [4028, 4339],
- [4002, 4362],
- [4024, 4377],
- [4008, 4423],
- [4044, 4454],
- ],
- ],
- [
- [
- [3239, 4455],
- [3210, 4462],
- [3221, 4473],
- [3236, 4473],
- [3239, 4455],
- ],
- ],
- [
- [
- [3210, 4514],
- [3193, 4513],
- [3208, 4554],
- [3204, 4535],
- [3210, 4514],
- ],
- ],
- [[[3319, 2429], [3321, 2426], [3321, 2426], [3319, 2429]]],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'IE',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.61,
- 'hc-middle-y': 0.6,
- 'hc-key': 'ie',
- 'hc-a2': 'IE',
- name: 'Ireland',
- labelrank: '3',
- 'country-abbrev': 'Ire.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'IRL',
- 'iso-a2': 'IE',
- 'woe-id': '23424803',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [[[135, 4725], [124, 4687], [114, 4713], [82, 4730], [135, 4725]]],
- [
- [
- [746, 4592],
- [766, 4559],
- [727, 4568],
- [735, 4525],
- [725, 4463],
- [742, 4439],
- [725, 4388],
- [719, 4263],
- [663, 4188],
- [656, 4154],
- [613, 4108],
- [605, 4057],
- [529, 4093],
- [512, 4071],
- [418, 4086],
- [382, 4055],
- [338, 4055],
- [299, 4028],
- [255, 4032],
- [161, 3989],
- [135, 4002],
- [56, 3997],
- [36, 4014],
- [-44, 4006],
- [16, 4039],
- [29, 4070],
- [-68, 4058],
- [-72, 4082],
- [1, 4110],
- [-65, 4105],
- [-114, 4157],
- [-78, 4178],
- [-14, 4185],
- [-5, 4200],
- [-102, 4220],
- [-96, 4241],
- [-38, 4256],
- [9, 4227],
- [26, 4264],
- [10, 4280],
- [61, 4287],
- [79, 4310],
- [137, 4294],
- [196, 4300],
- [197, 4332],
- [162, 4301],
- [95, 4324],
- [45, 4325],
- [112, 4353],
- [151, 4397],
- [130, 4404],
- [181, 4452],
- [237, 4436],
- [245, 4471],
- [130, 4499],
- [143, 4528],
- [92, 4528],
- [114, 4544],
- [74, 4553],
- [62, 4608],
- [121, 4610],
- [123, 4652],
- [182, 4646],
- [190, 4674],
- [130, 4679],
- [153, 4697],
- [142, 4783],
- [178, 4806],
- [276, 4765],
- [279, 4723],
- [304, 4760],
- [397, 4731],
- [372, 4762],
- [448, 4791],
- [481, 4819],
- [390, 4835],
- [376, 4862],
- [424, 4888],
- [462, 4881],
- [447, 4915],
- [493, 4969],
- [574, 4962],
- [602, 4975],
- [614, 4903],
- [622, 4975],
- [655, 4972],
- [651, 4996],
- [711, 4932],
- [652, 4902],
- [622, 4890],
- [581, 4822],
- [532, 4831],
- [538, 4795],
- [459, 4770],
- [496, 4684],
- [576, 4637],
- [639, 4710],
- [662, 4684],
- [668, 4641],
- [690, 4626],
- [685, 4588],
- [746, 4592],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'UA',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.58,
- 'hc-middle-y': 0.41,
- 'hc-key': 'ua',
- 'hc-a2': 'UA',
- name: 'Ukraine',
- labelrank: '3',
- 'country-abbrev': 'Ukr.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'UKR',
- 'iso-a2': 'UA',
- 'woe-id': '23424976',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [7455, 2386],
- [7493, 2450],
- [7501, 2459],
- [7470, 2406],
- [7455, 2386],
- ],
- ],
- [
- [
- [7966, 2676],
- [7994, 2681],
- [8031, 2696],
- [8047, 2688],
- [7966, 2676],
- ],
- ],
- [
- [
- [7864, 2673],
- [7779, 2665],
- [7752, 2669],
- [7846, 2675],
- [7864, 2673],
- ],
- ],
- [
- [
- [5798, 2837],
- [5793, 2890],
- [5819, 2928],
- [5819, 2970],
- [5841, 3040],
- [5904, 3029],
- [5867, 3073],
- [5861, 3130],
- [5837, 3173],
- [5900, 3310],
- [5994, 3455],
- [6035, 3467],
- [6051, 3510],
- [6010, 3586],
- [6038, 3595],
- [5989, 3635],
- [5976, 3670],
- [5922, 3719],
- [5907, 3769],
- [5926, 3810],
- [5970, 3801],
- [6008, 3848],
- [6025, 3898],
- [6082, 3911],
- [6159, 3947],
- [6262, 3963],
- [6378, 3967],
- [6462, 3957],
- [6519, 3969],
- [6542, 3930],
- [6616, 3948],
- [6634, 3914],
- [6639, 3956],
- [6687, 3955],
- [6704, 3988],
- [6737, 3959],
- [6780, 3978],
- [6813, 3942],
- [6825, 3992],
- [6865, 4022],
- [6918, 3960],
- [6961, 4007],
- [7005, 4004],
- [7053, 4033],
- [7103, 3994],
- [7140, 3984],
- [7149, 4017],
- [7106, 4084],
- [7114, 4151],
- [7144, 4237],
- [7196, 4244],
- [7228, 4275],
- [7278, 4286],
- [7305, 4275],
- [7359, 4306],
- [7358, 4378],
- [7461, 4389],
- [7495, 4437],
- [7544, 4450],
- [7559, 4434],
- [7598, 4468],
- [7657, 4432],
- [7685, 4375],
- [7746, 4356],
- [7755, 4326],
- [7716, 4303],
- [7775, 4229],
- [7767, 4189],
- [7843, 4209],
- [7871, 4200],
- [7929, 4227],
- [7978, 4187],
- [8043, 4111],
- [8032, 4093],
- [8058, 4058],
- [8114, 4038],
- [8131, 4069],
- [8177, 4085],
- [8220, 4059],
- [8238, 4073],
- [8284, 4059],
- [8325, 4117],
- [8405, 4175],
- [8447, 4151],
- [8492, 4103],
- [8559, 4075],
- [8583, 4097],
- [8571, 4130],
- [8675, 4127],
- [8730, 4116],
- [8766, 4149],
- [8806, 4125],
- [8846, 4141],
- [8909, 4111],
- [8957, 4148],
- [8960, 4107],
- [9002, 4059],
- [8986, 3990],
- [8948, 3965],
- [8976, 3938],
- [9015, 3941],
- [8984, 3894],
- [8990, 3846],
- [9021, 3859],
- [9090, 3791],
- [9081, 3739],
- [9100, 3657],
- [8932, 3589],
- [8934, 3537],
- [8873, 3489],
- [8881, 3393],
- [8905, 3344],
- [8790, 3293],
- [8767, 3220],
- [8748, 3228],
- [8710, 3193],
- [8685, 3138],
- [8660, 3145],
- [8577, 3079],
- [8526, 3055],
- [8457, 2941],
- [8432, 2953],
- [8409, 2894],
- [8369, 2847],
- [8405, 2786],
- [8463, 2728],
- [8547, 2667],
- [8583, 2657],
- [8621, 2681],
- [8638, 2731],
- [8673, 2711],
- [8692, 2749],
- [8781, 2772],
- [8770, 2707],
- [8791, 2664],
- [8756, 2632],
- [8717, 2630],
- [8685, 2601],
- [8631, 2618],
- [8538, 2498],
- [8491, 2476],
- [8444, 2431],
- [8433, 2370],
- [8377, 2301],
- [8328, 2290],
- [8246, 2312],
- [8278, 2340],
- [8252, 2382],
- [8259, 2429],
- [8232, 2470],
- [8195, 2485],
- [8168, 2467],
- [8079, 2498],
- [8006, 2470],
- [7998, 2503],
- [8048, 2572],
- [8195, 2713],
- [8166, 2718],
- [8144, 2762],
- [8135, 2728],
- [8065, 2759],
- [8051, 2728],
- [7988, 2706],
- [7938, 2672],
- [7887, 2672],
- [7783, 2705],
- [7811, 2745],
- [7721, 2746],
- [7876, 2771],
- [7896, 2816],
- [7798, 2795],
- [7766, 2837],
- [7766, 2785],
- [7693, 2768],
- [7604, 2727],
- [7565, 2690],
- [7579, 2673],
- [7551, 2554],
- [7503, 2462],
- [7472, 2461],
- [7470, 2423],
- [7440, 2382],
- [7406, 2436],
- [7425, 2339],
- [7451, 2343],
- [7468, 2301],
- [7457, 2266],
- [7445, 2302],
- [7373, 2312],
- [7282, 2223],
- [7186, 2225],
- [7150, 2258],
- [7203, 2283],
- [7190, 2327],
- [7228, 2391],
- [7223, 2426],
- [7259, 2448],
- [7256, 2502],
- [7235, 2519],
- [7222, 2574],
- [7258, 2609],
- [7279, 2560],
- [7319, 2610],
- [7355, 2583],
- [7369, 2620],
- [7405, 2591],
- [7448, 2613],
- [7404, 2644],
- [7384, 2725],
- [7302, 2743],
- [7303, 2774],
- [7273, 2849],
- [7248, 2829],
- [7174, 2884],
- [7170, 2984],
- [7141, 3008],
- [7112, 2987],
- [7070, 3029],
- [7015, 3026],
- [6981, 3041],
- [6926, 3031],
- [6850, 3073],
- [6803, 3068],
- [6749, 3021],
- [6685, 3015],
- [6652, 2968],
- [6596, 2941],
- [6585, 2873],
- [6419, 2808],
- [6400, 2767],
- [6360, 2740],
- [6283, 2792],
- [6223, 2767],
- [6132, 2776],
- [6052, 2762],
- [6000, 2782],
- [5962, 2732],
- [5928, 2776],
- [5901, 2766],
- [5872, 2803],
- [5848, 2799],
- [5824, 2842],
- [5798, 2837],
- ],
- [
- [7457, 2615],
- [7536, 2553],
- [7547, 2572],
- [7470, 2626],
- [7457, 2615],
- ],
- [
- [8226, 2751],
- [8230, 2774],
- [8290, 2767],
- [8336, 2746],
- [8341, 2716],
- [8384, 2771],
- [8375, 2732],
- [8448, 2722],
- [8511, 2641],
- [8573, 2639],
- [8552, 2659],
- [8454, 2726],
- [8380, 2809],
- [8334, 2833],
- [8341, 2785],
- [8309, 2776],
- [8319, 2817],
- [8239, 2836],
- [8226, 2787],
- [8209, 2801],
- [8147, 2787],
- [8226, 2751],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'FI',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.58,
- 'hc-middle-y': 0.72,
- 'hc-key': 'fi',
- 'hc-a2': 'FI',
- name: 'Finland',
- labelrank: '3',
- 'country-abbrev': 'Fin.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'FIN',
- 'iso-a2': 'FI',
- 'woe-id': '23424812',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [4993, 6096],
- [4976, 6089],
- [4970, 6088],
- [4977, 6101],
- [4993, 6096],
- ],
- ],
- [
- [
- [4992, 6109],
- [5004, 6120],
- [5016, 6114],
- [5011, 6102],
- [4992, 6109],
- ],
- ],
- [
- [
- [4939, 6118],
- [4961, 6095],
- [4955, 6084],
- [4930, 6101],
- [4939, 6118],
- ],
- ],
- [
- [
- [4959, 6128],
- [4966, 6130],
- [4964, 6111],
- [4954, 6118],
- [4959, 6128],
- ],
- ],
- [
- [
- [4873, 6139],
- [4861, 6117],
- [4851, 6132],
- [4858, 6155],
- [4873, 6139],
- ],
- ],
- [
- [
- [4902, 6194],
- [4957, 6174],
- [4947, 6141],
- [4922, 6163],
- [4931, 6115],
- [4891, 6105],
- [4867, 6158],
- [4902, 6194],
- ],
- ],
- [
- [
- [5277, 6163],
- [5280, 6143],
- [5272, 6140],
- [5263, 6163],
- [5277, 6163],
- ],
- ],
- [
- [
- [5158, 6164],
- [5156, 6153],
- [5142, 6149],
- [5147, 6172],
- [5158, 6164],
- ],
- ],
- [
- [
- [5191, 6183],
- [5175, 6160],
- [5167, 6161],
- [5170, 6182],
- [5191, 6183],
- ],
- ],
- [
- [
- [5210, 6170],
- [5199, 6182],
- [5212, 6195],
- [5210, 6182],
- [5210, 6170],
- ],
- ],
- [
- [
- [5722, 6296],
- [5708, 6311],
- [5710, 6317],
- [5727, 6305],
- [5722, 6296],
- ],
- ],
- [
- [
- [5326, 6209],
- [5314, 6149],
- [5284, 6144],
- [5272, 6198],
- [5326, 6209],
- ],
- ],
- [
- [
- [5243, 6212],
- [5215, 6212],
- [5220, 6227],
- [5248, 6226],
- [5243, 6212],
- ],
- ],
- [
- [
- [5261, 6239],
- [5254, 6226],
- [5245, 6233],
- [5256, 6244],
- [5261, 6239],
- ],
- ],
- [
- [
- [5222, 6241],
- [5239, 6247],
- [5240, 6243],
- [5227, 6236],
- [5222, 6241],
- ],
- ],
- [
- [
- [5196, 6222],
- [5169, 6235],
- [5179, 6259],
- [5200, 6229],
- [5196, 6222],
- ],
- ],
- [
- [
- [5114, 6275],
- [5115, 6255],
- [5107, 6259],
- [5095, 6277],
- [5114, 6275],
- ],
- ],
- [
- [
- [5079, 6373],
- [5065, 6389],
- [5072, 6396],
- [5084, 6386],
- [5079, 6373],
- ],
- ],
- [
- [
- [4937, 7061],
- [4954, 7049],
- [4977, 7043],
- [4964, 7024],
- [4937, 7061],
- ],
- ],
- [
- [
- [5064, 7083],
- [5069, 7094],
- [5088, 7090],
- [5076, 7072],
- [5064, 7083],
- ],
- ],
- [
- [
- [4972, 7074],
- [4968, 7065],
- [4954, 7075],
- [4968, 7088],
- [4972, 7074],
- ],
- ],
- [
- [
- [5135, 7240],
- [5138, 7228],
- [5131, 7222],
- [5121, 7233],
- [5135, 7240],
- ],
- ],
- [
- [
- [5322, 7664],
- [5299, 7629],
- [5286, 7647],
- [5295, 7662],
- [5322, 7664],
- ],
- ],
- [
- [
- [5345, 6190],
- [5331, 6207],
- [5327, 6241],
- [5281, 6204],
- [5269, 6214],
- [5287, 6256],
- [5239, 6250],
- [5173, 6282],
- [5163, 6310],
- [5142, 6267],
- [5098, 6304],
- [5093, 6379],
- [5067, 6421],
- [5089, 6423],
- [5097, 6469],
- [5073, 6573],
- [5103, 6565],
- [5073, 6596],
- [5037, 6676],
- [5017, 6687],
- [5013, 6796],
- [4984, 6806],
- [4983, 6864],
- [4964, 6884],
- [4975, 6939],
- [5002, 6971],
- [5001, 7006],
- [5028, 6998],
- [4995, 7046],
- [5044, 7070],
- [5050, 7045],
- [5104, 7098],
- [5073, 7134],
- [5102, 7148],
- [5112, 7214],
- [5146, 7203],
- [5158, 7255],
- [5202, 7289],
- [5195, 7332],
- [5255, 7403],
- [5254, 7443],
- [5289, 7495],
- [5290, 7539],
- [5322, 7603],
- [5356, 7625],
- [5385, 7602],
- [5393, 7628],
- [5369, 7651],
- [5398, 7650],
- [5360, 7689],
- [5364, 7730],
- [5355, 7797],
- [5315, 7825],
- [5266, 7823],
- [5238, 7871],
- [5223, 7856],
- [5191, 7866],
- [5161, 7928],
- [5117, 7968],
- [5095, 8033],
- [5116, 8076],
- [5106, 8131],
- [5115, 8151],
- [5047, 8242],
- [5053, 8324],
- [5012, 8336],
- [5020, 8369],
- [4997, 8455],
- [5012, 8473],
- [4945, 8521],
- [4925, 8569],
- [4893, 8592],
- [4833, 8607],
- [4808, 8603],
- [4787, 8627],
- [4732, 8654],
- [4661, 8729],
- [4625, 8745],
- [4635, 8767],
- [4669, 8753],
- [4680, 8769],
- [4662, 8795],
- [4687, 8832],
- [4725, 8830],
- [4776, 8761],
- [4823, 8711],
- [4835, 8679],
- [4900, 8683],
- [4923, 8666],
- [4976, 8706],
- [4991, 8740],
- [5041, 8717],
- [5079, 8712],
- [5106, 8686],
- [5129, 8714],
- [5127, 8767],
- [5171, 8799],
- [5178, 8840],
- [5159, 8892],
- [5162, 8948],
- [5153, 8993],
- [5157, 9048],
- [5173, 9058],
- [5190, 9125],
- [5225, 9140],
- [5252, 9135],
- [5290, 9182],
- [5320, 9205],
- [5361, 9160],
- [5392, 9139],
- [5476, 9121],
- [5515, 9065],
- [5485, 8979],
- [5490, 8946],
- [5509, 8926],
- [5467, 8875],
- [5508, 8866],
- [5495, 8771],
- [5543, 8678],
- [5621, 8661],
- [5680, 8597],
- [5730, 8568],
- [5735, 8514],
- [5708, 8441],
- [5685, 8340],
- [5696, 8295],
- [5773, 8209],
- [5793, 8170],
- [5847, 8111],
- [5891, 8036],
- [5907, 7989],
- [5863, 7968],
- [5889, 7883],
- [5880, 7852],
- [5913, 7851],
- [5920, 7819],
- [5900, 7782],
- [5939, 7733],
- [5974, 7739],
- [6000, 7703],
- [5979, 7678],
- [6002, 7628],
- [6081, 7597],
- [6094, 7531],
- [6072, 7463],
- [6049, 7441],
- [6136, 7380],
- [6185, 7369],
- [6251, 7333],
- [6261, 7312],
- [6320, 7265],
- [6313, 7140],
- [6287, 7068],
- [6260, 7026],
- [6195, 6828],
- [6171, 6788],
- [6144, 6709],
- [6100, 6652],
- [6036, 6533],
- [6004, 6463],
- [5961, 6425],
- [5915, 6445],
- [5896, 6417],
- [5808, 6365],
- [5760, 6377],
- [5762, 6313],
- [5734, 6307],
- [5718, 6339],
- [5702, 6300],
- [5659, 6290],
- [5659, 6272],
- [5580, 6239],
- [5570, 6198],
- [5551, 6217],
- [5449, 6162],
- [5424, 6157],
- [5404, 6119],
- [5356, 6102],
- [5399, 6143],
- [5377, 6144],
- [5394, 6176],
- [5356, 6176],
- [5346, 6188],
- [5344, 6176],
- [5342, 6158],
- [5333, 6178],
- [5345, 6190],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'SE',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.52,
- 'hc-middle-y': 0.34,
- 'hc-key': 'se',
- 'hc-a2': 'SE',
- name: 'Sweden',
- labelrank: '3',
- 'country-abbrev': 'Swe.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'SWE',
- 'iso-a2': 'SE',
- 'woe-id': '23424954',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [4581, 5272],
- [4583, 5238],
- [4535, 5043],
- [4529, 4978],
- [4507, 4942],
- [4497, 5032],
- [4523, 5130],
- [4538, 5137],
- [4566, 5259],
- [4581, 5272],
- ],
- ],
- [
- [
- [4891, 5495],
- [4910, 5490],
- [4866, 5467],
- [4871, 5487],
- [4891, 5495],
- ],
- ],
- [
- [
- [3744, 5428],
- [3733, 5400],
- [3712, 5397],
- [3709, 5423],
- [3744, 5428],
- ],
- ],
- [
- [
- [3751, 5482],
- [3754, 5447],
- [3710, 5435],
- [3696, 5456],
- [3732, 5493],
- [3751, 5482],
- ],
- ],
- [
- [
- [4523, 5546],
- [4520, 5537],
- [4508, 5566],
- [4529, 5555],
- [4523, 5546],
- ],
- ],
- [
- [
- [4726, 5760],
- [4721, 5748],
- [4707, 5740],
- [4707, 5752],
- [4726, 5760],
- ],
- ],
- [
- [
- [4703, 5774],
- [4693, 5756],
- [4681, 5760],
- [4687, 5772],
- [4703, 5774],
- ],
- ],
- [
- [
- [4628, 5737],
- [4620, 5738],
- [4617, 5776],
- [4629, 5757],
- [4628, 5737],
- ],
- ],
- [
- [
- [4738, 5788],
- [4732, 5773],
- [4722, 5779],
- [4737, 5805],
- [4738, 5788],
- ],
- ],
- [
- [
- [4740, 5856],
- [4743, 5848],
- [4741, 5838],
- [4713, 5856],
- [4740, 5856],
- ],
- ],
- [
- [
- [4755, 5930],
- [4740, 5895],
- [4731, 5912],
- [4741, 5929],
- [4755, 5930],
- ],
- ],
- [
- [
- [4779, 5953],
- [4788, 5967],
- [4786, 5948],
- [4777, 5952],
- [4779, 5953],
- ],
- ],
- [
- [
- [4790, 6015],
- [4791, 6010],
- [4776, 6006],
- [4783, 6029],
- [4790, 6015],
- ],
- ],
- [
- [
- [4688, 6191],
- [4710, 6150],
- [4685, 6166],
- [4681, 6205],
- [4688, 6191],
- ],
- ],
- [
- [
- [4563, 6851],
- [4572, 6851],
- [4569, 6834],
- [4556, 6831],
- [4563, 6851],
- ],
- ],
- [
- [
- [4889, 7192],
- [4898, 7199],
- [4898, 7178],
- [4891, 7169],
- [4889, 7192],
- ],
- ],
- [
- [
- [4984, 7737],
- [4981, 7732],
- [4983, 7723],
- [4969, 7739],
- [4984, 7737],
- ],
- ],
- [
- [
- [5014, 7760],
- [5021, 7760],
- [5017, 7746],
- [5009, 7757],
- [5014, 7760],
- ],
- ],
- [
- [
- [4737, 5337],
- [4781, 5427],
- [4820, 5465],
- [4829, 5444],
- [4861, 5466],
- [4876, 5446],
- [4837, 5414],
- [4844, 5332],
- [4867, 5322],
- [4830, 5286],
- [4840, 5271],
- [4792, 5232],
- [4785, 5177],
- [4761, 5169],
- [4776, 5205],
- [4743, 5265],
- [4752, 5301],
- [4737, 5337],
- ],
- ],
- [
- [
- [4625, 8745],
- [4661, 8729],
- [4732, 8654],
- [4787, 8627],
- [4808, 8603],
- [4833, 8607],
- [4893, 8592],
- [4925, 8569],
- [4945, 8521],
- [5012, 8473],
- [4997, 8455],
- [5020, 8369],
- [5012, 8336],
- [5053, 8324],
- [5047, 8242],
- [5115, 8151],
- [5106, 8131],
- [5116, 8076],
- [5095, 8033],
- [5117, 7968],
- [5161, 7928],
- [5191, 7866],
- [5167, 7842],
- [5135, 7857],
- [5097, 7848],
- [5074, 7811],
- [5013, 7844],
- [5017, 7815],
- [4988, 7838],
- [4970, 7786],
- [4995, 7744],
- [4913, 7774],
- [4968, 7739],
- [4929, 7731],
- [4946, 7697],
- [4891, 7682],
- [4922, 7655],
- [4919, 7613],
- [4879, 7513],
- [4863, 7506],
- [4936, 7408],
- [4882, 7303],
- [4872, 7224],
- [4838, 7205],
- [4820, 7152],
- [4789, 7151],
- [4754, 7104],
- [4749, 7076],
- [4715, 7110],
- [4726, 7067],
- [4695, 7077],
- [4698, 7043],
- [4673, 6991],
- [4656, 6999],
- [4622, 6975],
- [4596, 6913],
- [4609, 6891],
- [4560, 6858],
- [4538, 6870],
- [4560, 6814],
- [4521, 6754],
- [4482, 6780],
- [4472, 6739],
- [4501, 6688],
- [4488, 6609],
- [4501, 6542],
- [4458, 6497],
- [4477, 6471],
- [4490, 6327],
- [4518, 6257],
- [4502, 6243],
- [4558, 6233],
- [4571, 6196],
- [4609, 6224],
- [4619, 6200],
- [4713, 6127],
- [4678, 6140],
- [4708, 6106],
- [4754, 6099],
- [4781, 6040],
- [4762, 6002],
- [4798, 6008],
- [4748, 5958],
- [4717, 5912],
- [4660, 5882],
- [4732, 5872],
- [4721, 5897],
- [4770, 5878],
- [4763, 5862],
- [4703, 5859],
- [4729, 5811],
- [4676, 5782],
- [4660, 5738],
- [4637, 5738],
- [4632, 5796],
- [4611, 5784],
- [4615, 5719],
- [4522, 5639],
- [4417, 5642],
- [4446, 5623],
- [4502, 5632],
- [4528, 5600],
- [4497, 5582],
- [4512, 5550],
- [4514, 5494],
- [4490, 5435],
- [4465, 5421],
- [4515, 5366],
- [4494, 5370],
- [4518, 5303],
- [4498, 5275],
- [4491, 5215],
- [4513, 5179],
- [4452, 4992],
- [4446, 4944],
- [4421, 4893],
- [4415, 4914],
- [4373, 4924],
- [4334, 4902],
- [4274, 4907],
- [4232, 4897],
- [4247, 4864],
- [4201, 4861],
- [4161, 4798],
- [4160, 4770],
- [4191, 4714],
- [4163, 4674],
- [4114, 4683],
- [4016, 4653],
- [3962, 4674],
- [3956, 4718],
- [3976, 4746],
- [3953, 4767],
- [3880, 4916],
- [3927, 4904],
- [3897, 4958],
- [3939, 4970],
- [3934, 5026],
- [3912, 5025],
- [3889, 5074],
- [3853, 5099],
- [3798, 5256],
- [3779, 5227],
- [3775, 5302],
- [3742, 5330],
- [3738, 5364],
- [3762, 5474],
- [3756, 5503],
- [3707, 5491],
- [3692, 5535],
- [3665, 5507],
- [3672, 5577],
- [3645, 5689],
- [3660, 5725],
- [3693, 5697],
- [3695, 5669],
- [3727, 5677],
- [3747, 5773],
- [3725, 5862],
- [3756, 5924],
- [3748, 5954],
- [3790, 5963],
- [3842, 6028],
- [3836, 6080],
- [3850, 6144],
- [3816, 6212],
- [3797, 6278],
- [3857, 6297],
- [3880, 6395],
- [3841, 6445],
- [3814, 6454],
- [3777, 6495],
- [3794, 6644],
- [3760, 6746],
- [3767, 6790],
- [3759, 6845],
- [3774, 6868],
- [3741, 6952],
- [3767, 7001],
- [3762, 7043],
- [3825, 7151],
- [3894, 7193],
- [3986, 7174],
- [4012, 7233],
- [4002, 7302],
- [3940, 7341],
- [4015, 7494],
- [4036, 7564],
- [4035, 7678],
- [4042, 7721],
- [4024, 7804],
- [4086, 7816],
- [4134, 7856],
- [4123, 7920],
- [4149, 7954],
- [4186, 8043],
- [4225, 8096],
- [4224, 8141],
- [4182, 8204],
- [4188, 8230],
- [4219, 8248],
- [4246, 8357],
- [4298, 8421],
- [4367, 8387],
- [4391, 8447],
- [4379, 8560],
- [4406, 8575],
- [4432, 8556],
- [4475, 8562],
- [4579, 8529],
- [4608, 8572],
- [4575, 8586],
- [4600, 8626],
- [4600, 8702],
- [4570, 8732],
- [4625, 8745],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'RU',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.59,
- 'hc-middle-y': 0.45,
- 'hc-key': 'ru',
- 'hc-a2': 'RU',
- name: 'Russia',
- labelrank: '2',
- 'country-abbrev': 'Rus.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'RUS',
- 'iso-a2': 'RU',
- 'woe-id': '23424936',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [6161, 5739],
- [6243, 5717],
- [6237, 5759],
- [6194, 5799],
- [6149, 5776],
- [6124, 5800],
- [6149, 5876],
- [6122, 5986],
- [6102, 6021],
- [6106, 6046],
- [6108, 6072],
- [6115, 6109],
- [6124, 6119],
- [6130, 6125],
- [6119, 6126],
- [6130, 6125],
- [6143, 6139],
- [6126, 6160],
- [6111, 6169],
- [6091, 6230],
- [6097, 6261],
- [6148, 6234],
- [6154, 6294],
- [6188, 6280],
- [6221, 6303],
- [6234, 6358],
- [6388, 6364],
- [6381, 6399],
- [6342, 6396],
- [6317, 6431],
- [6199, 6407],
- [6158, 6445],
- [6127, 6446],
- [6091, 6487],
- [6133, 6467],
- [6112, 6530],
- [6078, 6516],
- [6061, 6476],
- [6004, 6463],
- [6036, 6533],
- [6100, 6652],
- [6144, 6709],
- [6171, 6788],
- [6195, 6828],
- [6260, 7026],
- [6287, 7068],
- [6313, 7140],
- [6320, 7265],
- [6261, 7312],
- [6251, 7333],
- [6185, 7369],
- [6136, 7380],
- [6049, 7441],
- [6072, 7463],
- [6094, 7531],
- [6081, 7597],
- [6002, 7628],
- [5979, 7678],
- [6000, 7703],
- [5974, 7739],
- [5939, 7733],
- [5900, 7782],
- [5920, 7819],
- [5913, 7851],
- [5880, 7852],
- [5889, 7883],
- [5863, 7968],
- [5907, 7989],
- [5891, 8036],
- [5847, 8111],
- [5793, 8170],
- [5773, 8209],
- [5696, 8295],
- [5685, 8340],
- [5708, 8441],
- [5735, 8514],
- [5730, 8568],
- [5680, 8597],
- [5621, 8661],
- [5543, 8678],
- [5495, 8771],
- [5508, 8866],
- [5467, 8875],
- [5509, 8926],
- [5532, 8960],
- [5529, 9010],
- [5581, 9050],
- [5597, 9081],
- [5599, 9138],
- [5628, 9120],
- [5664, 9128],
- [5659, 9172],
- [5638, 9209],
- [5685, 9209],
- [5710, 9193],
- [5747, 9204],
- [5726, 9251],
- [5760, 9239],
- [5736, 9277],
- [5746, 9292],
- [5820, 9258],
- [5849, 9267],
- [5881, 9239],
- [5863, 9208],
- [5786, 9206],
- [5779, 9175],
- [5814, 9193],
- [5836, 9176],
- [5895, 9185],
- [5883, 9145],
- [5922, 9186],
- [5944, 9186],
- [5940, 9121],
- [5965, 9116],
- [5964, 9160],
- [6046, 9184],
- [6077, 9175],
- [6141, 9187],
- [6142, 9207],
- [6217, 9203],
- [6264, 9182],
- [6330, 9171],
- [6416, 9139],
- [6441, 9140],
- [6559, 9072],
- [6574, 9088],
- [6704, 9042],
- [6761, 9048],
- [6812, 9033],
- [6845, 8998],
- [6855, 9022],
- [6905, 9008],
- [6932, 8944],
- [6953, 8939],
- [6971, 8891],
- [6994, 8896],
- [7027, 8838],
- [7034, 8774],
- [7005, 8628],
- [6980, 8579],
- [6916, 8501],
- [6838, 8442],
- [6764, 8422],
- [6626, 8430],
- [6572, 8412],
- [6444, 8403],
- [6359, 8434],
- [6325, 8401],
- [6282, 8430],
- [6243, 8419],
- [6195, 8445],
- [6200, 8416],
- [6106, 8461],
- [6091, 8493],
- [6038, 8497],
- [6028, 8483],
- [6073, 8455],
- [6081, 8424],
- [6125, 8392],
- [6171, 8394],
- [6159, 8338],
- [6198, 8349],
- [6253, 8343],
- [6252, 8307],
- [6375, 8282],
- [6437, 8236],
- [6462, 8184],
- [6427, 8204],
- [6459, 8148],
- [6442, 8071],
- [6519, 7996],
- [6512, 7973],
- [6552, 7949],
- [6545, 7915],
- [6581, 7883],
- [6562, 7858],
- [6666, 7811],
- [6708, 7845],
- [6743, 7813],
- [6782, 7804],
- [6801, 7775],
- [6860, 7771],
- [6919, 7785],
- [6942, 7770],
- [7012, 7842],
- [7014, 7867],
- [6972, 7910],
- [6972, 7940],
- [6922, 7956],
- [6891, 7915],
- [6859, 7919],
- [6783, 7980],
- [6758, 7982],
- [6722, 8032],
- [6761, 8060],
- [6742, 8112],
- [6761, 8128],
- [6859, 8118],
- [6917, 8089],
- [7033, 8099],
- [7129, 8080],
- [7147, 8130],
- [7199, 8115],
- [7240, 8128],
- [7178, 8223],
- [7129, 8251],
- [7045, 8320],
- [7033, 8381],
- [7069, 8437],
- [7094, 8533],
- [7145, 8580],
- [7172, 8670],
- [7178, 8751],
- [7236, 8752],
- [7235, 8729],
- [7303, 8780],
- [7377, 8744],
- [7353, 8690],
- [7399, 8745],
- [7436, 8729],
- [7476, 8684],
- [7413, 8795],
- [7402, 8910],
- [7374, 8932],
- [7369, 8976],
- [7324, 9009],
- [7263, 9008],
- [7238, 9051],
- [7210, 9191],
- [7194, 9213],
- [7159, 9318],
- [7139, 9335],
- [7019, 9368],
- [7027, 9390],
- [7093, 9384],
- [7223, 9450],
- [7299, 9457],
- [7325, 9430],
- [7402, 9407],
- [7463, 9341],
- [7403, 9282],
- [7343, 9236],
- [7359, 9198],
- [7338, 9166],
- [7360, 9106],
- [7449, 9098],
- [7514, 9042],
- [7542, 9038],
- [7591, 9079],
- [7600, 9064],
- [7667, 9142],
- [7689, 9179],
- [7655, 9230],
- [7644, 9306],
- [7615, 9329],
- [7639, 9386],
- [7683, 9428],
- [7668, 9467],
- [7697, 9484],
- [7728, 9545],
- [7765, 9652],
- [7773, 9713],
- [7794, 9714],
- [7870, 9851],
- [7872, 9851],
- [7892, 9835],
- [7898, 9795],
- [7929, 9801],
- [7936, 9851],
- [8020, 9851],
- [8021, 9833],
- [8055, 9851],
- [9732, 9851],
- [9732, 2582],
- [9688, 2568],
- [9659, 2542],
- [9662, 2497],
- [9589, 2522],
- [9379, 2615],
- [9285, 2633],
- [9196, 2619],
- [9107, 2671],
- [9032, 2637],
- [8968, 2690],
- [8806, 2713],
- [8834, 2751],
- [8827, 2795],
- [8896, 2789],
- [8901, 2761],
- [8965, 2827],
- [9012, 2828],
- [8978, 2893],
- [8970, 2849],
- [8950, 2912],
- [8981, 2954],
- [8977, 3041],
- [9017, 3090],
- [9065, 3076],
- [9077, 3107],
- [9018, 3114],
- [8967, 3143],
- [8941, 3127],
- [8863, 3194],
- [8924, 3204],
- [8970, 3283],
- [9035, 3333],
- [9069, 3391],
- [9105, 3400],
- [9077, 3467],
- [9026, 3422],
- [8905, 3344],
- [8881, 3393],
- [8873, 3489],
- [8934, 3537],
- [8932, 3589],
- [9100, 3657],
- [9081, 3739],
- [9090, 3791],
- [9021, 3859],
- [8990, 3846],
- [8984, 3894],
- [9015, 3941],
- [8976, 3938],
- [8948, 3965],
- [8986, 3990],
- [9002, 4059],
- [8960, 4107],
- [8957, 4148],
- [8909, 4111],
- [8846, 4141],
- [8806, 4125],
- [8766, 4149],
- [8730, 4116],
- [8675, 4127],
- [8571, 4130],
- [8583, 4097],
- [8559, 4075],
- [8492, 4103],
- [8447, 4151],
- [8405, 4175],
- [8325, 4117],
- [8284, 4059],
- [8238, 4073],
- [8220, 4059],
- [8177, 4085],
- [8131, 4069],
- [8114, 4038],
- [8058, 4058],
- [8032, 4093],
- [8043, 4111],
- [7978, 4187],
- [7929, 4227],
- [7871, 4200],
- [7843, 4209],
- [7767, 4189],
- [7775, 4229],
- [7716, 4303],
- [7755, 4326],
- [7746, 4356],
- [7685, 4375],
- [7657, 4432],
- [7598, 4468],
- [7559, 4434],
- [7544, 4450],
- [7495, 4437],
- [7461, 4389],
- [7358, 4378],
- [7359, 4306],
- [7305, 4275],
- [7278, 4286],
- [7227, 4334],
- [7171, 4478],
- [7114, 4511],
- [7126, 4570],
- [7180, 4585],
- [7246, 4572],
- [7290, 4620],
- [7320, 4704],
- [7266, 4719],
- [7254, 4763],
- [7183, 4772],
- [7129, 4751],
- [7118, 4826],
- [7024, 4849],
- [6987, 4904],
- [6960, 4909],
- [6966, 4950],
- [6889, 4976],
- [6884, 5017],
- [6907, 5053],
- [6852, 5115],
- [6861, 5155],
- [6840, 5206],
- [6818, 5198],
- [6759, 5240],
- [6715, 5246],
- [6663, 5224],
- [6610, 5166],
- [6584, 5189],
- [6576, 5237],
- [6517, 5241],
- [6489, 5207],
- [6449, 5241],
- [6400, 5217],
- [6371, 5238],
- [6371, 5279],
- [6340, 5347],
- [6284, 5395],
- [6262, 5427],
- [6240, 5414],
- [6248, 5510],
- [6241, 5547],
- [6181, 5574],
- [6177, 5602],
- [6151, 5595],
- [6159, 5682],
- [6194, 5705],
- [6161, 5739],
- ],
- [
- [5965, 9116],
- [5965, 9113],
- [5965, 9113],
- [5965, 9113],
- [5933, 9064],
- [5966, 9090],
- [5965, 9113],
- [5965, 9113],
- [5965, 9113],
- [5967, 9115],
- [5965, 9116],
- ],
- ],
- [
- [
- [6120, 6424],
- [6130, 6432],
- [6151, 6421],
- [6138, 6413],
- [6120, 6424],
- ],
- ],
- [
- [
- [6732, 7851],
- [6707, 7864],
- [6714, 7869],
- [6731, 7863],
- [6732, 7851],
- ],
- ],
- [
- [
- [6624, 8069],
- [6645, 8030],
- [6594, 8040],
- [6591, 8054],
- [6624, 8069],
- ],
- ],
- [
- [
- [6641, 8087],
- [6652, 8081],
- [6676, 8089],
- [6657, 8072],
- [6641, 8087],
- ],
- ],
- [
- [
- [7211, 8811],
- [7186, 8812],
- [7174, 8829],
- [7188, 8834],
- [7211, 8811],
- ],
- ],
- [
- [
- [7795, 9756],
- [7773, 9714],
- [7788, 9768],
- [7821, 9784],
- [7795, 9756],
- ],
- ],
- [
- [
- [6041, 9195],
- [6002, 9182],
- [5996, 9191],
- [6016, 9204],
- [6041, 9195],
- ],
- ],
- [
- [
- [5279, 4758],
- [5218, 4659],
- [5310, 4654],
- [5333, 4669],
- [5314, 4740],
- [5326, 4758],
- [5472, 4718],
- [5549, 4740],
- [5594, 4700],
- [5595, 4571],
- [5615, 4546],
- [5604, 4540],
- [5111, 4489],
- [5209, 4577],
- [5134, 4570],
- [5118, 4544],
- [5118, 4544],
- [5118, 4544],
- [5137, 4642],
- [5191, 4649],
- [5231, 4685],
- [5268, 4759],
- [5279, 4758],
- [5279, 4758],
- ],
- ],
- [
- [
- [5088, 4491],
- [5086, 4492],
- [5103, 4518],
- [5103, 4518],
- [5088, 4491],
- ],
- ],
- [
- [
- [7186, 8155],
- [7163, 8163],
- [7191, 8181],
- [7206, 8168],
- [7216, 8145],
- [7193, 8153],
- [7230, 8134],
- [7199, 8120],
- [7145, 8136],
- [7155, 8151],
- [7186, 8155],
- ],
- ],
- [
- [
- [5103, 4518],
- [5118, 4544],
- [5118, 4544],
- [5118, 4544],
- [5117, 4538],
- [5103, 4518],
- [5103, 4518],
- ],
- ],
- [
- [
- [7399, 9803],
- [7406, 9851],
- [7585, 9851],
- [7583, 9843],
- [7591, 9782],
- [7564, 9727],
- [7510, 9677],
- [7448, 9702],
- [7399, 9803],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'GB',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.18,
- 'hc-middle-y': 0.36,
- 'hc-key': 'gb',
- 'hc-a2': 'GB',
- name: 'United Kingdom',
- labelrank: '2',
- 'country-abbrev': 'U.K.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'GBR',
- 'iso-a2': 'GB',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [1201, 3080],
- [1215, 3052],
- [1176, 3062],
- [1178, 3081],
- [1201, 3080],
- ],
- ],
- [
- [
- [1423, 3486],
- [1461, 3456],
- [1411, 3434],
- [1382, 3463],
- [1423, 3486],
- ],
- ],
- [[[968, 4314], [973, 4313], [980, 4292], [962, 4305], [968, 4314]]],
- [
- [
- [1043, 4292],
- [1070, 4288],
- [1010, 4250],
- [979, 4298],
- [986, 4332],
- [1036, 4324],
- [1043, 4292],
- ],
- ],
- [
- [
- [1017, 4928],
- [983, 4948],
- [995, 5006],
- [1025, 4996],
- [1017, 4928],
- ],
- ],
- [
- [
- [1052, 5006],
- [1038, 5025],
- [1033, 5063],
- [1050, 5050],
- [1052, 5006],
- ],
- ],
- [
- [
- [885, 5073],
- [888, 5028],
- [851, 5030],
- [842, 5065],
- [811, 5045],
- [830, 5088],
- [886, 5105],
- [885, 5073],
- ],
- ],
- [[[898, 5153], [887, 5135], [874, 5134], [891, 5156], [898, 5153]]],
- [[[961, 5153], [957, 5129], [905, 5060], [894, 5095], [961, 5153]]],
- [[[803, 5273], [790, 5294], [831, 5297], [807, 5283], [803, 5273]]],
- [
- [
- [946, 5292],
- [953, 5262],
- [986, 5245],
- [991, 5217],
- [878, 5206],
- [922, 5254],
- [894, 5284],
- [935, 5305],
- [946, 5292],
- ],
- ],
- [[[862, 5305], [840, 5306], [862, 5321], [885, 5326], [862, 5305]]],
- [[[763, 5445], [745, 5435], [733, 5442], [754, 5460], [763, 5445]]],
- [[[935, 5412], [925, 5393], [906, 5415], [929, 5426], [935, 5412]]],
- [[[796, 5561], [811, 5523], [775, 5475], [769, 5517], [796, 5561]]],
- [[[811, 5581], [819, 5559], [805, 5558], [790, 5574], [811, 5581]]],
- [[[838, 5625], [833, 5583], [780, 5621], [795, 5637], [838, 5625]]],
- [
- [
- [1551, 5818],
- [1538, 5794],
- [1529, 5824],
- [1542, 5826],
- [1551, 5818],
- ],
- ],
- [
- [
- [1501, 5850],
- [1508, 5826],
- [1487, 5818],
- [1473, 5849],
- [1501, 5850],
- ],
- ],
- [
- [
- [1527, 5917],
- [1583, 5845],
- [1561, 5835],
- [1544, 5861],
- [1509, 5854],
- [1490, 5887],
- [1502, 5918],
- [1527, 5917],
- ],
- ],
- [
- [
- [1607, 5906],
- [1619, 5887],
- [1606, 5883],
- [1600, 5908],
- [1607, 5906],
- ],
- ],
- [
- [
- [1548, 5925],
- [1554, 5910],
- [1540, 5912],
- [1538, 5930],
- [1548, 5925],
- ],
- ],
- [
- [
- [1590, 5936],
- [1590, 5908],
- [1582, 5907],
- [1578, 5924],
- [1590, 5936],
- ],
- ],
- [
- [
- [1647, 5941],
- [1626, 5926],
- [1614, 5932],
- [1619, 5939],
- [1647, 5941],
- ],
- ],
- [
- [
- [1569, 5971],
- [1575, 5943],
- [1558, 5948],
- [1549, 5970],
- [1569, 5971],
- ],
- ],
- [
- [
- [1936, 6281],
- [1938, 6271],
- [1924, 6271],
- [1919, 6289],
- [1936, 6281],
- ],
- ],
- [
- [
- [1871, 6164],
- [1837, 6082],
- [1824, 6092],
- [1855, 6186],
- [1824, 6165],
- [1802, 6213],
- [1845, 6210],
- [1841, 6261],
- [1817, 6262],
- [1863, 6293],
- [1847, 6234],
- [1892, 6239],
- [1871, 6164],
- ],
- ],
- [
- [
- [1904, 6323],
- [1916, 6320],
- [1900, 6254],
- [1883, 6259],
- [1904, 6323],
- ],
- ],
- [
- [
- [1949, 6333],
- [1938, 6303],
- [1918, 6307],
- [1936, 6350],
- [1949, 6333],
- ],
- ],
- [
- [
- [1131, 3142],
- [1108, 3146],
- [1111, 3154],
- [1140, 3166],
- [1131, 3142],
- ],
- ],
- [
- [
- [1023, 4529],
- [989, 4528],
- [1011, 4570],
- [1049, 4605],
- [1082, 4612],
- [1081, 4575],
- [1023, 4529],
- ],
- ],
- [[[-207, -551], [-210, -560], [-212, -550], [-207, -551]]],
- [
- [
- [9095, -490],
- [9069, -492],
- [9034, -504],
- [9046, -485],
- [9095, -490],
- ],
- ],
- [
- [
- [9270, -328],
- [9261, -336],
- [9242, -332],
- [9236, -330],
- [9242, -332],
- [9237, -333],
- [9224, -339],
- [9224, -339],
- [9223, -335],
- [9219, -325],
- [9213, -324],
- [9213, -324],
- [9233, -318],
- [9262, -296],
- [9262, -296],
- [9261, -299],
- [9240, -313],
- [9270, -328],
- ],
- [
- [9232, -327],
- [9232, -324],
- [9229, -325],
- [9230, -327],
- [9232, -327],
- ],
- ],
- [
- [
- [1830, 3601],
- [1855, 3592],
- [1936, 3594],
- [1915, 3521],
- [1853, 3503],
- [1840, 3469],
- [1808, 3479],
- [1725, 3455],
- [1705, 3437],
- [1628, 3471],
- [1555, 3474],
- [1508, 3461],
- [1496, 3491],
- [1420, 3494],
- [1303, 3481],
- [1290, 3456],
- [1211, 3481],
- [1203, 3467],
- [1138, 3522],
- [1064, 3519],
- [1015, 3491],
- [1009, 3452],
- [970, 3405],
- [944, 3406],
- [885, 3466],
- [776, 3474],
- [761, 3448],
- [714, 3448],
- [705, 3410],
- [674, 3387],
- [633, 3446],
- [587, 3433],
- [597, 3463],
- [644, 3463],
- [728, 3514],
- [738, 3539],
- [784, 3551],
- [840, 3602],
- [856, 3659],
- [889, 3647],
- [934, 3701],
- [1004, 3698],
- [1069, 3668],
- [1134, 3657],
- [1142, 3691],
- [1204, 3735],
- [1257, 3796],
- [1213, 3761],
- [1161, 3761],
- [1116, 3721],
- [1053, 3733],
- [1010, 3807],
- [985, 3794],
- [929, 3803],
- [923, 3866],
- [870, 3863],
- [817, 3838],
- [812, 3872],
- [776, 3942],
- [818, 3963],
- [861, 3953],
- [986, 4004],
- [1026, 4074],
- [1039, 4180],
- [985, 4185],
- [961, 4159],
- [918, 4168],
- [955, 4201],
- [1001, 4221],
- [1048, 4271],
- [1194, 4279],
- [1224, 4247],
- [1220, 4281],
- [1244, 4294],
- [1243, 4326],
- [1286, 4374],
- [1263, 4388],
- [1277, 4433],
- [1324, 4481],
- [1291, 4516],
- [1249, 4491],
- [1225, 4578],
- [1205, 4617],
- [1266, 4714],
- [1298, 4729],
- [1242, 4752],
- [1231, 4719],
- [1190, 4724],
- [1165, 4701],
- [1132, 4713],
- [1101, 4753],
- [1094, 4689],
- [1025, 4757],
- [1006, 4744],
- [1011, 4691],
- [980, 4761],
- [1017, 4840],
- [1101, 4927],
- [1065, 4997],
- [1080, 5048],
- [1101, 5062],
- [1100, 5109],
- [1065, 5045],
- [1039, 5069],
- [1010, 5064],
- [1006, 5031],
- [970, 4998],
- [950, 4923],
- [903, 4915],
- [958, 5036],
- [958, 5101],
- [1001, 5152],
- [978, 5167],
- [1013, 5213],
- [1069, 5218],
- [1025, 5232],
- [1071, 5292],
- [995, 5245],
- [951, 5291],
- [971, 5303],
- [922, 5320],
- [932, 5335],
- [982, 5321],
- [988, 5362],
- [1054, 5463],
- [1027, 5491],
- [1037, 5556],
- [1062, 5536],
- [1051, 5587],
- [1058, 5629],
- [1093, 5647],
- [1112, 5620],
- [1131, 5643],
- [1167, 5614],
- [1127, 5685],
- [1148, 5677],
- [1145, 5729],
- [1192, 5729],
- [1185, 5753],
- [1229, 5825],
- [1273, 5794],
- [1400, 5774],
- [1464, 5786],
- [1521, 5771],
- [1498, 5737],
- [1500, 5708],
- [1474, 5681],
- [1325, 5602],
- [1324, 5566],
- [1362, 5562],
- [1274, 5481],
- [1382, 5502],
- [1401, 5520],
- [1462, 5496],
- [1488, 5501],
- [1546, 5480],
- [1637, 5469],
- [1654, 5405],
- [1613, 5366],
- [1592, 5311],
- [1554, 5245],
- [1512, 5212],
- [1491, 5171],
- [1429, 5146],
- [1432, 5118],
- [1469, 5088],
- [1431, 5067],
- [1403, 5076],
- [1362, 5041],
- [1296, 5041],
- [1370, 5007],
- [1422, 5032],
- [1448, 5025],
- [1519, 4972],
- [1558, 4882],
- [1586, 4863],
- [1584, 4742],
- [1603, 4625],
- [1628, 4576],
- [1711, 4521],
- [1730, 4457],
- [1778, 4405],
- [1750, 4383],
- [1756, 4346],
- [1794, 4266],
- [1763, 4265],
- [1724, 4304],
- [1739, 4267],
- [1791, 4218],
- [1813, 4142],
- [1806, 4103],
- [1743, 4053],
- [1803, 4017],
- [1843, 4064],
- [1911, 4056],
- [1963, 4037],
- [2022, 3985],
- [2034, 3900],
- [2002, 3841],
- [1988, 3788],
- [1913, 3738],
- [1898, 3712],
- [1872, 3728],
- [1844, 3654],
- [1767, 3648],
- [1809, 3635],
- [1781, 3619],
- [1823, 3604],
- [1821, 3625],
- [1842, 3614],
- [1848, 3599],
- [1830, 3601],
- ],
- ],
- [
- [
- [989, 5494],
- [996, 5479],
- [1046, 5460],
- [1035, 5441],
- [976, 5406],
- [984, 5454],
- [937, 5454],
- [922, 5503],
- [886, 5554],
- [922, 5560],
- [917, 5592],
- [950, 5557],
- [973, 5607],
- [990, 5569],
- [977, 5518],
- [989, 5495],
- [995, 5528],
- [1011, 5545],
- [999, 5516],
- [989, 5494],
- ],
- ],
- [
- [
- [902, 5785],
- [935, 5767],
- [936, 5789],
- [1032, 5834],
- [1037, 5783],
- [993, 5753],
- [989, 5703],
- [954, 5676],
- [875, 5640],
- [862, 5666],
- [909, 5691],
- [872, 5716],
- [902, 5785],
- ],
- ],
- [
- [
- [652, 4902],
- [679, 4888],
- [706, 4926],
- [738, 4913],
- [787, 4923],
- [845, 4898],
- [875, 4749],
- [902, 4722],
- [907, 4664],
- [888, 4631],
- [888, 4687],
- [855, 4642],
- [877, 4622],
- [822, 4614],
- [780, 4566],
- [746, 4592],
- [685, 4588],
- [690, 4626],
- [668, 4641],
- [662, 4684],
- [639, 4710],
- [576, 4637],
- [496, 4684],
- [459, 4770],
- [538, 4795],
- [532, 4831],
- [581, 4822],
- [622, 4890],
- [652, 4902],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'CY',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.19,
- 'hc-middle-y': 0.71,
- 'hc-key': 'cy',
- 'hc-a2': 'CY',
- name: 'Cyprus',
- labelrank: '5',
- 'country-abbrev': 'Cyp.',
- subregion: 'Western Asia',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'CYP',
- 'iso-a2': 'CY',
- 'woe-id': '-90',
- continent: 'Asia',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [9232, -327],
- [9230, -327],
- [9229, -325],
- [9232, -324],
- [9232, -327],
- ],
- ],
- [
- [
- [9291, -290],
- [9318, -310],
- [9270, -328],
- [9240, -313],
- [9261, -299],
- [9270, -299],
- [9291, -290],
- ],
- ],
- [
- [
- [9224, -339],
- [9216, -390],
- [9095, -490],
- [9046, -485],
- [9034, -504],
- [8942, -502],
- [8881, -414],
- [8923, -412],
- [8944, -370],
- [8955, -373],
- [8960, -360],
- [8968, -358],
- [9008, -383],
- [9083, -327],
- [9139, -309],
- [9162, -352],
- [9223, -335],
- [9224, -339],
- [9224, -339],
- ],
- ],
- [
- [
- [9236, -330],
- [9239, -329],
- [9245, -331],
- [9242, -332],
- [9236, -330],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'PT',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.48,
- 'hc-middle-y': 0.36,
- 'hc-key': 'pt',
- 'hc-a2': 'PT',
- name: 'Portugal',
- labelrank: '2',
- 'country-abbrev': 'Port.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'PRT',
- 'iso-a2': 'PT',
- 'woe-id': '23424925',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [-896, 403],
- [-979, 424],
- [-941, 512],
- [-901, 573],
- [-905, 609],
- [-867, 611],
- [-810, 675],
- [-731, 789],
- [-719, 832],
- [-619, 1023],
- [-609, 1164],
- [-611, 1246],
- [-566, 1304],
- [-534, 1320],
- [-441, 1327],
- [-423, 1288],
- [-463, 1252],
- [-457, 1228],
- [-354, 1228],
- [-338, 1204],
- [-257, 1193],
- [-226, 1223],
- [-107, 1176],
- [-122, 1124],
- [-54, 1057],
- [-134, 993],
- [-168, 993],
- [-220, 941],
- [-249, 936],
- [-238, 881],
- [-270, 768],
- [-262, 740],
- [-328, 703],
- [-304, 645],
- [-361, 562],
- [-476, 588],
- [-438, 511],
- [-449, 478],
- [-430, 404],
- [-393, 365],
- [-423, 324],
- [-479, 302],
- [-520, 224],
- [-481, 131],
- [-444, 129],
- [-473, 83],
- [-533, 84],
- [-559, 40],
- [-621, -20],
- [-621, -134],
- [-720, -162],
- [-761, -157],
- [-805, -120],
- [-906, -83],
- [-999, -89],
- [-917, 33],
- [-889, 160],
- [-903, 172],
- [-858, 231],
- [-850, 282],
- [-874, 333],
- [-937, 322],
- [-929, 397],
- [-896, 403],
- ],
- [
- [-896, 403],
- [-894, 402],
- [-893, 403],
- [-893, 403],
- [-893, 404],
- [-850, 411],
- [-856, 453],
- [-893, 404],
- [-893, 404],
- [-893, 404],
- [-896, 403],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'GR',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.21,
- 'hc-middle-y': 0.4,
- 'hc-key': 'gr',
- 'hc-a2': 'GR',
- name: 'Greece',
- labelrank: '3',
- 'country-abbrev': 'Greece',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'GRC',
- 'iso-a2': 'GR',
- 'woe-id': '23424833',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [7604, -670],
- [7608, -677],
- [7582, -697],
- [7580, -676],
- [7604, -670],
- ],
- ],
- [
- [
- [7631, -545],
- [7629, -606],
- [7642, -663],
- [7609, -618],
- [7631, -545],
- ],
- ],
- [
- [
- [6616, -642],
- [6582, -647],
- [6584, -593],
- [6626, -630],
- [6616, -642],
- ],
- ],
- [
- [
- [7823, -305],
- [7805, -407],
- [7756, -489],
- [7713, -417],
- [7751, -352],
- [7823, -305],
- ],
- ],
- [
- [
- [7182, -475],
- [7175, -489],
- [7154, -487],
- [7169, -474],
- [7182, -475],
- ],
- ],
- [
- [
- [7617, -345],
- [7625, -359],
- [7626, -370],
- [7604, -352],
- [7617, -345],
- ],
- ],
- [
- [
- [7396, -360],
- [7364, -392],
- [7351, -370],
- [7370, -374],
- [7396, -360],
- ],
- ],
- [
- [
- [7712, -275],
- [7723, -272],
- [7731, -295],
- [7701, -292],
- [7712, -275],
- ],
- ],
- [
- [
- [6934, -404],
- [6941, -431],
- [6892, -453],
- [6893, -421],
- [6934, -404],
- ],
- ],
- [
- [
- [7139, -377],
- [7138, -400],
- [7105, -385],
- [7113, -363],
- [7139, -377],
- ],
- ],
- [
- [
- [7551, -257],
- [7495, -300],
- [7519, -256],
- [7566, -224],
- [7551, -257],
- ],
- ],
- [
- [
- [7286, -293],
- [7247, -333],
- [7238, -318],
- [7267, -285],
- [7286, -293],
- ],
- ],
- [
- [
- [6946, -321],
- [6976, -342],
- [6967, -360],
- [6944, -328],
- [6946, -321],
- ],
- ],
- [
- [
- [7485, -202],
- [7503, -208],
- [7503, -228],
- [7477, -234],
- [7485, -202],
- ],
- ],
- [
- [
- [7046, -318],
- [7057, -279],
- [7081, -269],
- [7077, -311],
- [7046, -318],
- ],
- ],
- [
- [
- [7438, -167],
- [7460, -189],
- [7449, -192],
- [7429, -173],
- [7438, -167],
- ],
- ],
- [
- [
- [7161, -259],
- [7139, -319],
- [7104, -279],
- [7140, -233],
- [7161, -259],
- ],
- ],
- [
- [
- [6896, -283],
- [6906, -283],
- [6912, -303],
- [6889, -307],
- [6896, -283],
- ],
- ],
- [
- [
- [6670, -285],
- [6660, -295],
- [6632, -304],
- [6650, -286],
- [6670, -285],
- ],
- ],
- [
- [
- [6880, -220],
- [6863, -259],
- [6857, -221],
- [6868, -204],
- [6880, -220],
- ],
- ],
- [
- [
- [7078, -153],
- [7107, -159],
- [7096, -177],
- [7075, -183],
- [7078, -153],
- ],
- ],
- [
- [
- [6996, -203],
- [6979, -218],
- [6972, -209],
- [6981, -174],
- [6996, -203],
- ],
- ],
- [
- [
- [7008, -125],
- [7047, -125],
- [7046, -153],
- [6989, -133],
- [7008, -125],
- ],
- ],
- [
- [
- [6842, -149],
- [6851, -166],
- [6828, -198],
- [6824, -172],
- [6842, -149],
- ],
- ],
- [[[7294, -67], [7241, -114], [7221, -109], [7237, -79], [7294, -67]]],
- [
- [
- [6639, -190],
- [6630, -189],
- [6618, -168],
- [6650, -162],
- [6639, -190],
- ],
- ],
- [
- [
- [7431, 3],
- [7459, -6],
- [7400, -38],
- [7346, -22],
- [7402, 12],
- [7431, 3],
- ],
- ],
- [
- [
- [5994, -248],
- [6054, -276],
- [6018, -305],
- [5965, -262],
- [5994, -248],
- ],
- ],
- [[[6938, -60], [6962, -58], [6965, -113], [6899, -63], [6938, -60]]],
- [
- [
- [6631, -102],
- [6637, -116],
- [6621, -132],
- [6605, -127],
- [6631, -102],
- ],
- ],
- [
- [
- [5926, -73],
- [5995, -185],
- [5927, -181],
- [5891, -111],
- [5913, -123],
- [5926, -73],
- ],
- ],
- [
- [
- [7173, 194],
- [7198, 187],
- [7214, 118],
- [7188, 68],
- [7149, 90],
- [7172, 121],
- [7124, 182],
- [7173, 194],
- ],
- ],
- [[[5956, -15], [5922, -30], [5914, -10], [5935, 41], [5956, -15]]],
- [[[6817, 208], [6846, 190], [6813, 180], [6793, 232], [6817, 208]]],
- [[[6561, 245], [6551, 230], [6536, 231], [6549, 254], [6561, 245]]],
- [[[6579, 258], [6627, 240], [6618, 227], [6600, 227], [6579, 258]]],
- [[[6648, 257], [6642, 262], [6651, 293], [6659, 292], [6648, 257]]],
- [
- [
- [7202, 424],
- [7264, 345],
- [7228, 323],
- [7167, 325],
- [7082, 354],
- [7082, 378],
- [7183, 438],
- [7202, 424],
- ],
- ],
- [[[5724, 291], [5711, 248], [5738, 200], [5658, 269], [5724, 291]]],
- [
- [
- [6940, 577],
- [6935, 505],
- [6895, 528],
- [6865, 505],
- [6853, 550],
- [6911, 552],
- [6940, 577],
- ],
- ],
- [[[6963, 721], [6969, 703], [6950, 692], [6917, 710], [6963, 721]]],
- [[[6760, 718], [6700, 711], [6718, 764], [6750, 754], [6760, 718]]],
- [
- [
- [5744, 264],
- [5783, 255],
- [5827, 318],
- [5801, 361],
- [5871, 401],
- [5896, 516],
- [5934, 546],
- [5929, 587],
- [5950, 612],
- [5928, 589],
- [5909, 607],
- [5912, 616],
- [5923, 633],
- [5943, 639],
- [5999, 661],
- [6044, 658],
- [6105, 737],
- [6162, 765],
- [6216, 761],
- [6280, 788],
- [6278, 832],
- [6313, 844],
- [6357, 845],
- [6390, 877],
- [6462, 882],
- [6547, 945],
- [6645, 975],
- [6671, 941],
- [6714, 926],
- [6735, 946],
- [6831, 918],
- [6884, 953],
- [6956, 965],
- [7004, 992],
- [7003, 1040],
- [6967, 1083],
- [7026, 1104],
- [7089, 1088],
- [7113, 1024],
- [7056, 973],
- [7081, 892],
- [7028, 811],
- [7001, 837],
- [6909, 825],
- [6792, 840],
- [6750, 785],
- [6693, 803],
- [6656, 791],
- [6651, 758],
- [6606, 719],
- [6537, 721],
- [6516, 691],
- [6698, 573],
- [6683, 554],
- [6645, 591],
- [6585, 607],
- [6539, 585],
- [6563, 553],
- [6615, 538],
- [6624, 490],
- [6572, 514],
- [6539, 553],
- [6475, 558],
- [6465, 536],
- [6562, 466],
- [6484, 465],
- [6455, 542],
- [6391, 557],
- [6335, 600],
- [6368, 621],
- [6348, 644],
- [6284, 580],
- [6307, 555],
- [6293, 492],
- [6303, 460],
- [6382, 390],
- [6406, 343],
- [6451, 320],
- [6522, 239],
- [6421, 274],
- [6413, 212],
- [6469, 187],
- [6414, 128],
- [6373, 132],
- [6413, 100],
- [6482, 95],
- [6494, 74],
- [6546, 82],
- [6561, 42],
- [6600, 46],
- [6643, 14],
- [6710, 7],
- [6747, -23],
- [6728, -40],
- [6771, -143],
- [6765, -175],
- [6694, -139],
- [6639, -80],
- [6561, -117],
- [6518, -163],
- [6560, -167],
- [6567, -224],
- [6600, -217],
- [6659, -253],
- [6559, -304],
- [6568, -274],
- [6468, -264],
- [6483, -304],
- [6544, -376],
- [6599, -470],
- [6582, -493],
- [6638, -568],
- [6536, -516],
- [6516, -478],
- [6473, -492],
- [6460, -548],
- [6474, -588],
- [6434, -581],
- [6425, -525],
- [6361, -440],
- [6312, -460],
- [6310, -537],
- [6267, -521],
- [6258, -476],
- [6217, -423],
- [6238, -381],
- [6193, -305],
- [6081, -240],
- [6125, -148],
- [6122, -119],
- [6178, -130],
- [6208, -82],
- [6250, -63],
- [6320, -96],
- [6430, -113],
- [6480, -142],
- [6472, -115],
- [6552, -79],
- [6528, -63],
- [6420, -38],
- [6387, -49],
- [6346, -11],
- [6348, -44],
- [6254, -40],
- [6153, -91],
- [6112, -64],
- [6067, -102],
- [6041, -37],
- [5962, 66],
- [6037, 101],
- [5958, 121],
- [5942, 72],
- [5926, 106],
- [5844, 158],
- [5781, 221],
- [5788, 250],
- [5744, 264],
- ],
- ],
- [[[5953, -97], [5966, -114], [5953, -98], [5953, -97], [5953, -97]]],
- [
- [
- [6631, 30],
- [6624, 62],
- [6509, 131],
- [6453, 139],
- [6520, 197],
- [6560, 161],
- [6601, 132],
- [6666, 117],
- [6715, 125],
- [6761, 87],
- [6757, 52],
- [6808, -13],
- [6863, -2],
- [6866, -56],
- [6825, -66],
- [6725, 22],
- [6724, 42],
- [6631, 30],
- ],
- ],
- [
- [
- [7250, -790],
- [7315, -764],
- [7313, -812],
- [7342, -826],
- [7437, -752],
- [7449, -825],
- [7297, -867],
- [7180, -916],
- [7094, -936],
- [7076, -889],
- [6997, -876],
- [6845, -893],
- [6778, -888],
- [6780, -790],
- [6813, -814],
- [6809, -762],
- [6836, -803],
- [6921, -771],
- [6955, -832],
- [7059, -791],
- [7119, -780],
- [7144, -796],
- [7250, -790],
- ],
- ],
- [[[5953, -97], [5936, -73], [5949, -66], [5953, -97], [5953, -97]]],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'LT',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.6,
- 'hc-middle-y': 0.49,
- 'hc-key': 'lt',
- 'hc-a2': 'LT',
- name: 'Lithuania',
- labelrank: '5',
- 'country-abbrev': 'Lith.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'LTU',
- 'iso-a2': 'LT',
- 'woe-id': '23424875',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [5326, 4758],
- [5306, 4834],
- [5272, 4895],
- [5254, 4987],
- [5276, 4994],
- [5291, 5038],
- [5404, 5112],
- [5500, 5110],
- [5529, 5132],
- [5570, 5111],
- [5594, 5133],
- [5661, 5141],
- [5729, 5127],
- [5783, 5137],
- [5842, 5202],
- [5891, 5137],
- [5981, 5144],
- [5992, 5129],
- [6081, 5080],
- [6104, 5054],
- [6159, 5044],
- [6157, 4946],
- [6218, 4942],
- [6193, 4890],
- [6147, 4887],
- [6131, 4831],
- [6080, 4791],
- [6092, 4704],
- [6071, 4634],
- [6100, 4633],
- [6118, 4585],
- [6086, 4576],
- [6065, 4618],
- [6026, 4598],
- [6009, 4557],
- [5972, 4554],
- [5971, 4502],
- [5952, 4504],
- [5908, 4464],
- [5875, 4477],
- [5784, 4444],
- [5756, 4450],
- [5735, 4513],
- [5628, 4561],
- [5615, 4546],
- [5595, 4571],
- [5594, 4700],
- [5549, 4740],
- [5472, 4718],
- [5326, 4758],
- ],
- ],
- [
- [
- [5268, 4759],
- [5285, 4824],
- [5279, 4758],
- [5279, 4758],
- [5268, 4759],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'SI',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.38,
- 'hc-middle-y': 0.62,
- 'hc-key': 'si',
- 'hc-a2': 'SI',
- name: 'Slovenia',
- labelrank: '6',
- 'country-abbrev': 'Slo.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'SVN',
- 'iso-a2': 'SI',
- 'woe-id': '23424945',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [4209, 1816],
- [4208, 1830],
- [4232, 1848],
- [4269, 1861],
- [4203, 1909],
- [4209, 1955],
- [4176, 1965],
- [4209, 2017],
- [4152, 2046],
- [4216, 2116],
- [4358, 2100],
- [4386, 2084],
- [4444, 2153],
- [4485, 2170],
- [4565, 2165],
- [4597, 2198],
- [4673, 2197],
- [4685, 2247],
- [4720, 2250],
- [4777, 2150],
- [4722, 2143],
- [4734, 2110],
- [4614, 2053],
- [4602, 2023],
- [4625, 2002],
- [4624, 1943],
- [4581, 1934],
- [4542, 1900],
- [4570, 1837],
- [4525, 1818],
- [4428, 1842],
- [4403, 1876],
- [4369, 1822],
- [4286, 1828],
- [4271, 1801],
- [4209, 1816],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'BA',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.49,
- 'hc-middle-y': 0.42,
- 'hc-key': 'ba',
- 'hc-a2': 'BA',
- name: 'Bosnia and Herzegovina',
- labelrank: '5',
- 'country-abbrev': 'B.H.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'BIH',
- 'iso-a2': 'BA',
- 'woe-id': '23424761',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [5115, 1130],
- [5093, 1141],
- [5098, 1143],
- [5095, 1184],
- [5062, 1201],
- [5019, 1256],
- [5010, 1291],
- [4976, 1298],
- [4890, 1365],
- [4881, 1385],
- [4775, 1482],
- [4747, 1571],
- [4668, 1659],
- [4662, 1750],
- [4705, 1771],
- [4754, 1721],
- [4781, 1716],
- [4805, 1774],
- [4867, 1774],
- [4893, 1807],
- [4950, 1773],
- [4966, 1787],
- [5030, 1773],
- [5051, 1788],
- [5085, 1763],
- [5120, 1791],
- [5164, 1799],
- [5229, 1777],
- [5259, 1787],
- [5308, 1729],
- [5333, 1734],
- [5363, 1755],
- [5405, 1749],
- [5402, 1704],
- [5369, 1637],
- [5388, 1571],
- [5489, 1511],
- [5411, 1478],
- [5467, 1428],
- [5478, 1370],
- [5448, 1380],
- [5419, 1354],
- [5360, 1334],
- [5401, 1286],
- [5364, 1294],
- [5321, 1259],
- [5320, 1192],
- [5282, 1179],
- [5293, 1114],
- [5315, 1079],
- [5293, 1053],
- [5161, 1114],
- [5115, 1130],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'MC',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.47,
- 'hc-middle-y': 0.63,
- 'hc-key': 'mc',
- 'hc-a2': 'MC',
- name: 'Monaco',
- labelrank: '6',
- 'country-abbrev': 'Mco.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'MCO',
- 'iso-a2': 'MC',
- 'woe-id': '23424892',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [2937, 1301],
- [2930, 1294],
- [2922, 1295],
- [2931, 1307],
- [2937, 1301],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'AL',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.3,
- 'hc-middle-y': 0.65,
- 'hc-key': 'al',
- 'hc-a2': 'AL',
- name: 'Albania',
- labelrank: '6',
- 'country-abbrev': 'Alb.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'ALB',
- 'iso-a2': 'AL',
- 'woe-id': '23424742',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [[[5907, 643], [5905, 642], [5902, 648], [5906, 645], [5907, 643]]],
- [
- [
- [5827, 689],
- [5837, 642],
- [5854, 641],
- [5874, 648],
- [5902, 648],
- [5899, 643],
- [5909, 635],
- [5909, 634],
- [5900, 609],
- [5912, 616],
- [5909, 607],
- [5928, 589],
- [5924, 584],
- [5929, 587],
- [5934, 546],
- [5896, 516],
- [5871, 401],
- [5801, 361],
- [5827, 318],
- [5783, 255],
- [5744, 264],
- [5742, 309],
- [5697, 364],
- [5606, 400],
- [5597, 470],
- [5552, 520],
- [5567, 589],
- [5586, 602],
- [5563, 670],
- [5575, 704],
- [5543, 743],
- [5572, 801],
- [5568, 869],
- [5519, 873],
- [5511, 904],
- [5511, 941],
- [5532, 960],
- [5508, 1011],
- [5560, 1115],
- [5593, 1064],
- [5642, 1094],
- [5686, 1036],
- [5749, 1011],
- [5782, 938],
- [5778, 915],
- [5765, 818],
- [5792, 778],
- [5782, 752],
- [5827, 689],
- ],
- ],
- [[[5503, 1000], [5503, 997], [5502, 997], [5503, 1000]]],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'CNM',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.66,
- 'hc-middle-y': 0.61,
- 'hc-key': 'cnm',
- 'hc-a2': 'CN',
- name: 'Cyprus No Mans Area',
- labelrank: '9',
- 'country-abbrev': null,
- subregion: 'Western Asia',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': '-99',
- 'iso-a2': null,
- 'woe-id': '-99',
- continent: 'Asia',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [9288, -289],
- [9288, -289],
- [9291, -290],
- [9270, -299],
- [9261, -299],
- [9262, -296],
- [9276, -291],
- [9288, -289],
- ],
- ],
- [
- [
- [8944, -370],
- [8947, -367],
- [8947, -367],
- [8952, -370],
- [8956, -361],
- [8958, -360],
- [8960, -360],
- [8955, -373],
- [8944, -370],
- ],
- ],
- [
- [
- [8968, -358],
- [8968, -358],
- [8973, -358],
- [9012, -377],
- [9084, -318],
- [9132, -299],
- [9160, -323],
- [9213, -324],
- [9213, -324],
- [9219, -325],
- [9223, -335],
- [9162, -352],
- [9139, -309],
- [9083, -327],
- [9008, -383],
- [8968, -358],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'NC',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.59,
- 'hc-middle-y': 0.61,
- 'hc-key': 'nc',
- 'hc-a2': 'NC',
- name: 'Northern Cyprus',
- labelrank: '6',
- 'country-abbrev': 'N. Cy.',
- subregion: 'Western Asia',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': '-99',
- 'iso-a2': 'NC',
- 'woe-id': '-90',
- continent: 'Asia',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [9160, -323],
- [9132, -299],
- [9084, -318],
- [9012, -377],
- [8973, -358],
- [9011, -353],
- [9001, -280],
- [9126, -260],
- [9253, -171],
- [9359, -64],
- [9360, -79],
- [9246, -235],
- [9288, -289],
- [9276, -291],
- [9262, -296],
- [9262, -296],
- [9233, -318],
- [9213, -324],
- [9213, -324],
- [9160, -323],
- ],
- ],
- [
- [
- [8947, -367],
- [8951, -364],
- [8956, -361],
- [8952, -370],
- [8947, -367],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'RS',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.47,
- 'hc-middle-y': 0.51,
- 'hc-key': 'rs',
- 'hc-a2': 'RS',
- name: 'Republic of Serbia',
- labelrank: '5',
- 'country-abbrev': 'Serb.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'SRB',
- 'iso-a2': 'RS',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [5333, 1734],
- [5332, 1816],
- [5399, 1831],
- [5390, 1853],
- [5313, 1878],
- [5308, 1936],
- [5265, 2001],
- [5272, 2037],
- [5302, 2065],
- [5342, 2061],
- [5393, 2121],
- [5468, 2131],
- [5533, 2124],
- [5557, 2094],
- [5657, 2038],
- [5663, 1963],
- [5714, 1924],
- [5826, 1887],
- [5805, 1838],
- [5841, 1827],
- [5814, 1802],
- [5881, 1761],
- [5954, 1762],
- [5997, 1730],
- [6036, 1801],
- [6113, 1770],
- [6059, 1736],
- [6116, 1666],
- [6105, 1620],
- [6067, 1588],
- [6066, 1533],
- [6118, 1443],
- [6229, 1382],
- [6192, 1283],
- [6146, 1270],
- [6144, 1181],
- [6170, 1136],
- [6140, 1104],
- [6119, 1116],
- [5976, 1056],
- [5999, 1185],
- [5913, 1193],
- [5912, 1226],
- [5882, 1230],
- [5837, 1284],
- [5780, 1298],
- [5772, 1323],
- [5728, 1295],
- [5747, 1275],
- [5690, 1184],
- [5685, 1207],
- [5636, 1220],
- [5590, 1254],
- [5522, 1261],
- [5419, 1354],
- [5448, 1380],
- [5478, 1370],
- [5467, 1428],
- [5411, 1478],
- [5489, 1511],
- [5388, 1571],
- [5369, 1637],
- [5402, 1704],
- [5405, 1749],
- [5363, 1755],
- [5333, 1734],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'RO',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.45,
- 'hc-middle-y': 0.54,
- 'hc-key': 'ro',
- 'hc-a2': 'RO',
- name: 'Romania',
- labelrank: '3',
- 'country-abbrev': 'Rom.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'ROU',
- 'iso-a2': 'RO',
- 'woe-id': '23424933',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [6059, 1736],
- [6113, 1770],
- [6036, 1801],
- [5997, 1730],
- [5954, 1762],
- [5881, 1761],
- [5814, 1802],
- [5841, 1827],
- [5805, 1838],
- [5826, 1887],
- [5714, 1924],
- [5663, 1963],
- [5657, 2038],
- [5557, 2094],
- [5533, 2124],
- [5575, 2150],
- [5605, 2141],
- [5645, 2188],
- [5703, 2199],
- [5724, 2302],
- [5756, 2338],
- [5767, 2428],
- [5790, 2458],
- [5814, 2573],
- [5856, 2646],
- [5943, 2693],
- [5962, 2732],
- [6000, 2782],
- [6052, 2762],
- [6132, 2776],
- [6223, 2767],
- [6283, 2792],
- [6360, 2740],
- [6400, 2767],
- [6419, 2808],
- [6585, 2873],
- [6596, 2941],
- [6652, 2968],
- [6687, 2976],
- [6729, 2959],
- [6775, 2912],
- [6821, 2832],
- [6884, 2789],
- [6953, 2710],
- [7003, 2689],
- [7070, 2596],
- [7089, 2531],
- [7089, 2403],
- [7132, 2304],
- [7114, 2288],
- [7150, 2258],
- [7186, 2225],
- [7282, 2223],
- [7373, 2312],
- [7445, 2302],
- [7457, 2266],
- [7476, 2165],
- [7469, 2151],
- [7380, 2113],
- [7337, 2172],
- [7324, 2132],
- [7352, 2077],
- [7322, 2035],
- [7356, 2049],
- [7322, 1963],
- [7352, 1873],
- [7353, 1794],
- [7278, 1781],
- [7223, 1793],
- [7199, 1830],
- [7163, 1808],
- [7144, 1825],
- [7089, 1809],
- [7055, 1833],
- [7006, 1837],
- [6838, 1749],
- [6780, 1652],
- [6698, 1611],
- [6610, 1620],
- [6512, 1615],
- [6444, 1587],
- [6264, 1592],
- [6183, 1561],
- [6168, 1606],
- [6193, 1640],
- [6116, 1666],
- [6115, 1666],
- [6059, 1736],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'ME',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.29,
- 'hc-middle-y': 0.67,
- 'hc-key': 'me',
- 'hc-a2': 'ME',
- name: 'Montenegro',
- labelrank: '6',
- 'country-abbrev': 'Mont.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'MNE',
- 'iso-a2': 'ME',
- 'woe-id': '20069817',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [5642, 1094],
- [5593, 1064],
- [5560, 1115],
- [5508, 1011],
- [5505, 1006],
- [5503, 1000],
- [5502, 997],
- [5456, 988],
- [5511, 941],
- [5511, 904],
- [5519, 873],
- [5473, 892],
- [5463, 922],
- [5396, 986],
- [5377, 978],
- [5344, 1027],
- [5311, 1013],
- [5298, 1030],
- [5293, 1053],
- [5315, 1079],
- [5293, 1114],
- [5282, 1179],
- [5320, 1192],
- [5321, 1259],
- [5364, 1294],
- [5401, 1286],
- [5360, 1334],
- [5419, 1354],
- [5522, 1261],
- [5590, 1254],
- [5636, 1220],
- [5685, 1207],
- [5690, 1184],
- [5626, 1144],
- [5642, 1094],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'LI',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.62,
- 'hc-middle-y': 0.51,
- 'hc-key': 'li',
- 'hc-a2': 'LI',
- name: 'Liechtenstein',
- labelrank: '6',
- 'country-abbrev': 'Liech.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'LIE',
- 'iso-a2': 'LI',
- 'woe-id': '23424879',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [3383, 2312],
- [3402, 2266],
- [3395, 2252],
- [3374, 2254],
- [3383, 2312],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'AT',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.68,
- 'hc-middle-y': 0.5,
- 'hc-key': 'at',
- 'hc-a2': 'AT',
- name: 'Austria',
- labelrank: '4',
- 'country-abbrev': 'Aust.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'AUT',
- 'iso-a2': 'AT',
- 'woe-id': '23424750',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [3395, 2252],
- [3402, 2266],
- [3383, 2312],
- [3409, 2354],
- [3390, 2383],
- [3413, 2380],
- [3422, 2394],
- [3467, 2387],
- [3538, 2323],
- [3566, 2362],
- [3558, 2401],
- [3632, 2385],
- [3670, 2350],
- [3724, 2353],
- [3794, 2409],
- [3900, 2416],
- [3902, 2446],
- [3965, 2426],
- [4016, 2438],
- [4021, 2406],
- [4072, 2390],
- [4066, 2454],
- [4041, 2456],
- [4059, 2492],
- [4008, 2569],
- [4062, 2613],
- [4132, 2649],
- [4151, 2710],
- [4189, 2693],
- [4205, 2765],
- [4257, 2717],
- [4304, 2710],
- [4329, 2736],
- [4376, 2724],
- [4392, 2779],
- [4415, 2776],
- [4422, 2848],
- [4476, 2844],
- [4558, 2815],
- [4583, 2821],
- [4627, 2791],
- [4689, 2788],
- [4702, 2808],
- [4787, 2794],
- [4804, 2763],
- [4792, 2692],
- [4860, 2595],
- [4836, 2551],
- [4854, 2508],
- [4801, 2493],
- [4763, 2511],
- [4726, 2482],
- [4770, 2475],
- [4777, 2426],
- [4737, 2393],
- [4759, 2293],
- [4717, 2291],
- [4685, 2247],
- [4673, 2197],
- [4597, 2198],
- [4565, 2165],
- [4485, 2170],
- [4444, 2153],
- [4386, 2084],
- [4358, 2100],
- [4216, 2116],
- [4122, 2120],
- [3956, 2154],
- [3895, 2240],
- [3913, 2265],
- [3823, 2232],
- [3737, 2234],
- [3694, 2212],
- [3676, 2170],
- [3567, 2197],
- [3550, 2234],
- [3500, 2191],
- [3450, 2218],
- [3449, 2240],
- [3395, 2252],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'SK',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.4,
- 'hc-middle-y': 0.46,
- 'hc-key': 'sk',
- 'hc-a2': 'SK',
- name: 'Slovakia',
- labelrank: '6',
- 'country-abbrev': 'Svk.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'SVK',
- 'iso-a2': 'SK',
- 'woe-id': '23424877',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [4860, 2595],
- [4792, 2692],
- [4804, 2763],
- [4839, 2840],
- [4910, 2834],
- [4968, 2872],
- [5005, 2912],
- [5019, 2981],
- [5077, 3047],
- [5131, 3062],
- [5160, 3030],
- [5198, 3039],
- [5241, 3102],
- [5283, 3050],
- [5310, 3051],
- [5313, 2997],
- [5371, 2995],
- [5381, 3035],
- [5411, 3063],
- [5481, 3078],
- [5533, 3055],
- [5545, 3087],
- [5585, 3108],
- [5692, 3104],
- [5736, 3063],
- [5841, 3040],
- [5819, 2970],
- [5819, 2928],
- [5793, 2890],
- [5798, 2837],
- [5724, 2806],
- [5656, 2860],
- [5600, 2830],
- [5535, 2843],
- [5479, 2822],
- [5459, 2748],
- [5381, 2693],
- [5328, 2714],
- [5305, 2677],
- [5184, 2641],
- [5168, 2619],
- [5187, 2581],
- [5078, 2545],
- [4999, 2535],
- [4947, 2553],
- [4897, 2596],
- [4860, 2595],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'HU',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.45,
- 'hc-middle-y': 0.54,
- 'hc-key': 'hu',
- 'hc-a2': 'HU',
- name: 'Hungary',
- labelrank: '5',
- 'country-abbrev': 'Hun.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'HUN',
- 'iso-a2': 'HU',
- 'woe-id': '23424844',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [4777, 2150],
- [4720, 2250],
- [4685, 2247],
- [4717, 2291],
- [4759, 2293],
- [4737, 2393],
- [4777, 2426],
- [4770, 2475],
- [4726, 2482],
- [4763, 2511],
- [4801, 2493],
- [4854, 2508],
- [4836, 2551],
- [4860, 2595],
- [4897, 2596],
- [4947, 2553],
- [4999, 2535],
- [5078, 2545],
- [5187, 2581],
- [5168, 2619],
- [5184, 2641],
- [5305, 2677],
- [5328, 2714],
- [5381, 2693],
- [5459, 2748],
- [5479, 2822],
- [5535, 2843],
- [5600, 2830],
- [5656, 2860],
- [5724, 2806],
- [5798, 2837],
- [5824, 2842],
- [5848, 2799],
- [5872, 2803],
- [5901, 2766],
- [5928, 2776],
- [5962, 2732],
- [5943, 2693],
- [5856, 2646],
- [5814, 2573],
- [5790, 2458],
- [5767, 2428],
- [5756, 2338],
- [5724, 2302],
- [5703, 2199],
- [5645, 2188],
- [5605, 2141],
- [5575, 2150],
- [5533, 2124],
- [5468, 2131],
- [5393, 2121],
- [5342, 2061],
- [5302, 2065],
- [5272, 2037],
- [5227, 2026],
- [5180, 1971],
- [5138, 1979],
- [5070, 1968],
- [5008, 2009],
- [4959, 2009],
- [4924, 2054],
- [4877, 2075],
- [4851, 2116],
- [4777, 2150],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'AD',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.48,
- 'hc-middle-y': 0.52,
- 'hc-key': 'ad',
- 'hc-a2': 'AD',
- name: 'Andorra',
- labelrank: '6',
- 'country-abbrev': 'And.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'AND',
- 'iso-a2': 'AD',
- 'woe-id': '23424744',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [1691, 1033],
- [1645, 1016],
- [1634, 1067],
- [1697, 1064],
- [1691, 1033],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'LU',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.5,
- 'hc-middle-y': 0.58,
- 'hc-key': 'lu',
- 'hc-a2': 'LU',
- name: 'Luxembourg',
- labelrank: '6',
- 'country-abbrev': 'Lux.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'LUX',
- 'iso-a2': 'LU',
- 'woe-id': '23424881',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [2791, 2962],
- [2752, 2975],
- [2719, 2961],
- [2688, 2991],
- [2708, 3020],
- [2680, 3091],
- [2737, 3172],
- [2759, 3155],
- [2774, 3087],
- [2825, 3058],
- [2791, 2962],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'CH',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.51,
- 'hc-middle-y': 0.49,
- 'hc-key': 'ch',
- 'hc-a2': 'CH',
- name: 'Switzerland',
- labelrank: '4',
- 'country-abbrev': 'Switz.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'CHE',
- 'iso-a2': 'CH',
- 'woe-id': '23424957',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [3006, 2412],
- [3174, 2408],
- [3164, 2431],
- [3205, 2468],
- [3264, 2425],
- [3318, 2430],
- [3319, 2429],
- [3321, 2426],
- [3321, 2426],
- [3378, 2377],
- [3390, 2383],
- [3409, 2354],
- [3383, 2312],
- [3374, 2254],
- [3395, 2252],
- [3449, 2240],
- [3450, 2218],
- [3500, 2191],
- [3550, 2234],
- [3567, 2197],
- [3551, 2141],
- [3562, 2101],
- [3495, 2121],
- [3486, 2071],
- [3509, 2022],
- [3486, 2010],
- [3470, 2049],
- [3389, 2031],
- [3365, 2089],
- [3330, 2087],
- [3331, 2036],
- [3272, 1939],
- [3287, 1913],
- [3255, 1898],
- [3243, 1967],
- [3196, 1984],
- [3162, 2022],
- [3166, 2075],
- [3091, 2025],
- [3097, 1988],
- [3039, 1928],
- [2981, 1950],
- [2908, 1923],
- [2876, 1937],
- [2844, 1995],
- [2835, 2080],
- [2852, 2092],
- [2803, 2112],
- [2741, 2096],
- [2720, 2051],
- [2731, 2035],
- [2664, 2007],
- [2704, 2071],
- [2688, 2089],
- [2706, 2139],
- [2768, 2184],
- [2773, 2237],
- [2805, 2247],
- [2897, 2346],
- [2862, 2351],
- [2892, 2392],
- [2964, 2369],
- [3006, 2412],
- [3006, 2412],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'BE',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.53,
- 'hc-middle-y': 0.37,
- 'hc-key': 'be',
- 'hc-a2': 'BE',
- name: 'Belgium',
- labelrank: '2',
- 'country-abbrev': 'Belg.',
- subregion: 'Western Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'BEL',
- 'iso-a2': 'BE',
- 'woe-id': '23424757',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [2759, 3155],
- [2737, 3172],
- [2680, 3091],
- [2708, 3020],
- [2688, 2991],
- [2629, 2982],
- [2536, 3076],
- [2519, 3074],
- [2510, 3129],
- [2529, 3175],
- [2492, 3162],
- [2486, 3135],
- [2443, 3122],
- [2390, 3138],
- [2408, 3217],
- [2377, 3244],
- [2310, 3245],
- [2310, 3281],
- [2244, 3311],
- [2227, 3382],
- [2161, 3376],
- [2129, 3413],
- [2124, 3485],
- [2239, 3543],
- [2280, 3553],
- [2285, 3515],
- [2330, 3523],
- [2380, 3495],
- [2437, 3537],
- [2452, 3509],
- [2444, 3537],
- [2467, 3530],
- [2486, 3564],
- [2511, 3546],
- [2537, 3566],
- [2561, 3534],
- [2581, 3557],
- [2614, 3492],
- [2660, 3498],
- [2723, 3456],
- [2690, 3344],
- [2746, 3338],
- [2778, 3297],
- [2779, 3261],
- [2804, 3257],
- [2809, 3209],
- [2765, 3182],
- [2759, 3155],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'KV',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.89,
- 'hc-middle-y': 0.48,
- 'hc-key': 'kv',
- 'hc-a2': 'KV',
- name: 'Kosovo',
- labelrank: '6',
- 'country-abbrev': 'Kos.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': '-99',
- 'iso-a2': 'KV',
- 'woe-id': '-90',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [5778, 915],
- [5782, 938],
- [5749, 1011],
- [5686, 1036],
- [5642, 1094],
- [5626, 1144],
- [5690, 1184],
- [5747, 1275],
- [5728, 1295],
- [5772, 1323],
- [5780, 1298],
- [5837, 1284],
- [5882, 1230],
- [5912, 1226],
- [5913, 1193],
- [5999, 1185],
- [5976, 1056],
- [5945, 1049],
- [5913, 1002],
- [5878, 1026],
- [5813, 977],
- [5816, 931],
- [5778, 915],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'PL',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.56,
- 'hc-middle-y': 0.5,
- 'hc-key': 'pl',
- 'hc-a2': 'PL',
- name: 'Poland',
- labelrank: '3',
- 'country-abbrev': 'Pol.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'POL',
- 'iso-a2': 'PL',
- 'woe-id': '23424923',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [
- [
- [5615, 4546],
- [5628, 4561],
- [5735, 4513],
- [5756, 4450],
- [5791, 4360],
- [5868, 4241],
- [5895, 4126],
- [5890, 4104],
- [5817, 4042],
- [5789, 3973],
- [5882, 3930],
- [5894, 3906],
- [5886, 3834],
- [5907, 3769],
- [5922, 3719],
- [5976, 3670],
- [5989, 3635],
- [6038, 3595],
- [6010, 3586],
- [6051, 3510],
- [6035, 3467],
- [5994, 3455],
- [5900, 3310],
- [5837, 3173],
- [5861, 3130],
- [5867, 3073],
- [5904, 3029],
- [5841, 3040],
- [5736, 3063],
- [5692, 3104],
- [5585, 3108],
- [5545, 3087],
- [5533, 3055],
- [5481, 3078],
- [5411, 3063],
- [5381, 3035],
- [5371, 2995],
- [5313, 2997],
- [5310, 3051],
- [5283, 3050],
- [5241, 3102],
- [5198, 3039],
- [5160, 3030],
- [5131, 3062],
- [5114, 3108],
- [5088, 3112],
- [5067, 3169],
- [4967, 3187],
- [4931, 3173],
- [4880, 3222],
- [4880, 3250],
- [4830, 3248],
- [4797, 3277],
- [4739, 3283],
- [4762, 3230],
- [4718, 3186],
- [4643, 3260],
- [4615, 3272],
- [4648, 3301],
- [4636, 3338],
- [4591, 3335],
- [4470, 3369],
- [4453, 3363],
- [4435, 3411],
- [4378, 3418],
- [4385, 3380],
- [4354, 3378],
- [4385, 3489],
- [4367, 3549],
- [4327, 3566],
- [4322, 3611],
- [4295, 3647],
- [4322, 3727],
- [4307, 3777],
- [4276, 3824],
- [4292, 3870],
- [4196, 3943],
- [4198, 3974],
- [4231, 4002],
- [4238, 4067],
- [4206, 4188],
- [4249, 4181],
- [4266, 4236],
- [4192, 4239],
- [4187, 4247],
- [4193, 4256],
- [4220, 4252],
- [4288, 4290],
- [4511, 4381],
- [4576, 4464],
- [4637, 4484],
- [4699, 4531],
- [4787, 4562],
- [4858, 4575],
- [4910, 4497],
- [4917, 4464],
- [4989, 4447],
- [5048, 4464],
- [5048, 4464],
- [5027, 4450],
- [5056, 4436],
- [5111, 4489],
- [5604, 4540],
- [5615, 4546],
- ],
- ],
- [
- [
- [5086, 4492],
- [5088, 4491],
- [5060, 4472],
- [5060, 4472],
- [5086, 4492],
- ],
- ],
- [
- [
- [5048, 4464],
- [5060, 4472],
- [5060, 4472],
- [5050, 4465],
- [5048, 4464],
- [5048, 4464],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'MK',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.49,
- 'hc-middle-y': 0.49,
- 'hc-key': 'mk',
- 'hc-a2': 'MK',
- name: 'Macedonia',
- labelrank: '6',
- 'country-abbrev': 'Mkd.',
- subregion: 'Southern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'MKD',
- 'iso-a2': 'MK',
- 'woe-id': '23424890',
- continent: 'Europe',
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: [
- [[[5909, 635], [5910, 635], [5909, 634], [5909, 635]]],
- [
- [
- [6140, 1104],
- [6189, 1058],
- [6262, 1037],
- [6313, 957],
- [6303, 916],
- [6313, 844],
- [6278, 832],
- [6280, 788],
- [6216, 761],
- [6162, 765],
- [6105, 737],
- [6044, 658],
- [5999, 661],
- [5943, 639],
- [5902, 680],
- [5907, 643],
- [5906, 645],
- [5902, 648],
- [5902, 648],
- [5874, 648],
- [5854, 641],
- [5866, 703],
- [5827, 689],
- [5782, 752],
- [5792, 778],
- [5765, 818],
- [5778, 915],
- [5816, 931],
- [5813, 977],
- [5878, 1026],
- [5913, 1002],
- [5945, 1049],
- [5976, 1056],
- [6119, 1116],
- [6140, 1104],
- ],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'LV',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.68,
- 'hc-middle-y': 0.47,
- 'hc-key': 'lv',
- 'hc-a2': 'LV',
- name: 'Latvia',
- labelrank: '5',
- 'country-abbrev': 'Lat.',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'LVA',
- 'iso-a2': 'LV',
- 'woe-id': '23424874',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [6371, 5238],
- [6337, 5217],
- [6307, 5156],
- [6308, 5118],
- [6209, 5104],
- [6192, 5064],
- [6159, 5044],
- [6104, 5054],
- [6081, 5080],
- [5992, 5129],
- [5981, 5144],
- [5891, 5137],
- [5842, 5202],
- [5783, 5137],
- [5729, 5127],
- [5661, 5141],
- [5594, 5133],
- [5570, 5111],
- [5529, 5132],
- [5500, 5110],
- [5404, 5112],
- [5291, 5038],
- [5276, 4994],
- [5254, 4987],
- [5233, 5037],
- [5222, 5208],
- [5266, 5271],
- [5258, 5343],
- [5288, 5426],
- [5332, 5447],
- [5414, 5508],
- [5433, 5460],
- [5517, 5412],
- [5549, 5343],
- [5626, 5314],
- [5663, 5335],
- [5716, 5410],
- [5714, 5443],
- [5669, 5588],
- [5699, 5617],
- [5801, 5677],
- [5861, 5642],
- [5926, 5637],
- [5934, 5617],
- [6022, 5561],
- [6071, 5605],
- [6151, 5595],
- [6177, 5602],
- [6181, 5574],
- [6241, 5547],
- [6248, 5510],
- [6240, 5414],
- [6262, 5427],
- [6284, 5395],
- [6340, 5347],
- [6371, 5279],
- [6371, 5238],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'BY',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.48,
- 'hc-middle-y': 0.57,
- 'hc-key': 'by',
- 'hc-a2': 'BY',
- name: 'Belarus',
- labelrank: '4',
- 'country-abbrev': 'Bela.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'BLR',
- 'iso-a2': 'BY',
- 'woe-id': '23424765',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [6159, 5044],
- [6192, 5064],
- [6209, 5104],
- [6308, 5118],
- [6307, 5156],
- [6337, 5217],
- [6371, 5238],
- [6400, 5217],
- [6449, 5241],
- [6489, 5207],
- [6517, 5241],
- [6576, 5237],
- [6584, 5189],
- [6610, 5166],
- [6663, 5224],
- [6715, 5246],
- [6759, 5240],
- [6818, 5198],
- [6840, 5206],
- [6861, 5155],
- [6852, 5115],
- [6907, 5053],
- [6884, 5017],
- [6889, 4976],
- [6966, 4950],
- [6960, 4909],
- [6987, 4904],
- [7024, 4849],
- [7118, 4826],
- [7129, 4751],
- [7183, 4772],
- [7254, 4763],
- [7266, 4719],
- [7320, 4704],
- [7290, 4620],
- [7246, 4572],
- [7180, 4585],
- [7126, 4570],
- [7114, 4511],
- [7171, 4478],
- [7227, 4334],
- [7278, 4286],
- [7228, 4275],
- [7196, 4244],
- [7144, 4237],
- [7114, 4151],
- [7106, 4084],
- [7149, 4017],
- [7140, 3984],
- [7103, 3994],
- [7053, 4033],
- [7005, 4004],
- [6961, 4007],
- [6918, 3960],
- [6865, 4022],
- [6825, 3992],
- [6813, 3942],
- [6780, 3978],
- [6737, 3959],
- [6704, 3988],
- [6687, 3955],
- [6639, 3956],
- [6634, 3914],
- [6616, 3948],
- [6542, 3930],
- [6519, 3969],
- [6462, 3957],
- [6378, 3967],
- [6262, 3963],
- [6159, 3947],
- [6082, 3911],
- [6025, 3898],
- [6008, 3848],
- [5970, 3801],
- [5926, 3810],
- [5907, 3769],
- [5886, 3834],
- [5894, 3906],
- [5882, 3930],
- [5789, 3973],
- [5817, 4042],
- [5890, 4104],
- [5895, 4126],
- [5868, 4241],
- [5791, 4360],
- [5756, 4450],
- [5784, 4444],
- [5875, 4477],
- [5908, 4464],
- [5952, 4504],
- [5971, 4502],
- [5972, 4554],
- [6009, 4557],
- [6026, 4598],
- [6065, 4618],
- [6086, 4576],
- [6118, 4585],
- [6100, 4633],
- [6071, 4634],
- [6092, 4704],
- [6080, 4791],
- [6131, 4831],
- [6147, 4887],
- [6193, 4890],
- [6218, 4942],
- [6157, 4946],
- [6159, 5044],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'IS',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.59,
- 'hc-middle-y': 0.68,
- 'hc-key': 'is',
- 'hc-a2': 'IS',
- name: 'Iceland',
- labelrank: '3',
- 'country-abbrev': 'Iceland',
- subregion: 'Northern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'ISL',
- 'iso-a2': 'IS',
- 'woe-id': '23424845',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [23, 7765],
- [-31, 7764],
- [-42, 7780],
- [-86, 7762],
- [-64, 7754],
- [-101, 7722],
- [-209, 7727],
- [-271, 7767],
- [-318, 7818],
- [-367, 7843],
- [-388, 7915],
- [-443, 7984],
- [-435, 8008],
- [-470, 7988],
- [-517, 8017],
- [-554, 8023],
- [-582, 8046],
- [-631, 8058],
- [-594, 8126],
- [-590, 8089],
- [-546, 8080],
- [-466, 8107],
- [-487, 8122],
- [-442, 8153],
- [-483, 8140],
- [-497, 8153],
- [-458, 8196],
- [-397, 8218],
- [-488, 8200],
- [-493, 8230],
- [-466, 8281],
- [-602, 8368],
- [-616, 8355],
- [-649, 8375],
- [-644, 8415],
- [-596, 8393],
- [-542, 8384],
- [-532, 8401],
- [-506, 8378],
- [-474, 8390],
- [-473, 8368],
- [-433, 8366],
- [-370, 8331],
- [-345, 8373],
- [-387, 8362],
- [-439, 8405],
- [-423, 8419],
- [-332, 8436],
- [-332, 8465],
- [-371, 8459],
- [-387, 8509],
- [-413, 8504],
- [-455, 8548],
- [-556, 8542],
- [-556, 8561],
- [-613, 8605],
- [-567, 8624],
- [-538, 8578],
- [-544, 8617],
- [-519, 8656],
- [-469, 8575],
- [-488, 8630],
- [-478, 8675],
- [-451, 8648],
- [-466, 8697],
- [-455, 8712],
- [-432, 8685],
- [-436, 8718],
- [-402, 8730],
- [-378, 8700],
- [-381, 8676],
- [-344, 8653],
- [-330, 8618],
- [-337, 8579],
- [-309, 8641],
- [-344, 8686],
- [-333, 8728],
- [-353, 8755],
- [-336, 8773],
- [-272, 8752],
- [-251, 8712],
- [-259, 8681],
- [-233, 8670],
- [-219, 8634],
- [-229, 8605],
- [-208, 8600],
- [-197, 8559],
- [-209, 8522],
- [-269, 8453],
- [-246, 8455],
- [-266, 8427],
- [-254, 8402],
- [-265, 8355],
- [-201, 8434],
- [-164, 8447],
- [-167, 8384],
- [-110, 8440],
- [-96, 8526],
- [-87, 8540],
- [-45, 8536],
- [-48, 8465],
- [-33, 8447],
- [-39, 8415],
- [-19, 8396],
- [-2, 8460],
- [31, 8489],
- [61, 8471],
- [71, 8495],
- [101, 8497],
- [121, 8449],
- [109, 8419],
- [128, 8404],
- [123, 8338],
- [146, 8358],
- [140, 8395],
- [160, 8464],
- [199, 8439],
- [214, 8374],
- [237, 8373],
- [282, 8419],
- [307, 8391],
- [363, 8386],
- [381, 8410],
- [391, 8477],
- [452, 8463],
- [449, 8428],
- [472, 8410],
- [453, 8383],
- [483, 8328],
- [504, 8358],
- [527, 8353],
- [569, 8373],
- [592, 8356],
- [537, 8346],
- [535, 8321],
- [502, 8309],
- [508, 8287],
- [554, 8273],
- [551, 8240],
- [502, 8190],
- [566, 8182],
- [553, 8170],
- [604, 8087],
- [624, 8078],
- [583, 8028],
- [604, 8005],
- [589, 7946],
- [569, 7931],
- [532, 7974],
- [550, 7914],
- [516, 7902],
- [505, 7876],
- [444, 7877],
- [405, 7807],
- [384, 7815],
- [347, 7800],
- [289, 7807],
- [204, 7798],
- [142, 7765],
- [77, 7752],
- [64, 7775],
- [45, 7750],
- [23, 7765],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'MD',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.47,
- 'hc-middle-y': 0.3,
- 'hc-key': 'md',
- 'hc-a2': 'MD',
- name: 'Moldova',
- labelrank: '6',
- 'country-abbrev': 'Mda.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'MDA',
- 'iso-a2': 'MD',
- 'woe-id': '23424885',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [7150, 2258],
- [7114, 2288],
- [7132, 2304],
- [7089, 2403],
- [7089, 2531],
- [7070, 2596],
- [7003, 2689],
- [6953, 2710],
- [6884, 2789],
- [6821, 2832],
- [6775, 2912],
- [6729, 2959],
- [6687, 2976],
- [6652, 2968],
- [6685, 3015],
- [6749, 3021],
- [6803, 3068],
- [6850, 3073],
- [6926, 3031],
- [6981, 3041],
- [7015, 3026],
- [7070, 3029],
- [7112, 2987],
- [7141, 3008],
- [7170, 2984],
- [7174, 2884],
- [7248, 2829],
- [7273, 2849],
- [7303, 2774],
- [7302, 2743],
- [7384, 2725],
- [7404, 2644],
- [7448, 2613],
- [7405, 2591],
- [7369, 2620],
- [7355, 2583],
- [7319, 2610],
- [7279, 2560],
- [7258, 2609],
- [7222, 2574],
- [7235, 2519],
- [7256, 2502],
- [7259, 2448],
- [7223, 2426],
- [7228, 2391],
- [7190, 2327],
- [7203, 2283],
- [7150, 2258],
- ],
- ],
- },
- },
- {
- type: 'Feature',
- id: 'CZ',
- properties: {
- 'hc-group': 'admin0',
- 'hc-middle-x': 0.39,
- 'hc-middle-y': 0.51,
- 'hc-key': 'cz',
- 'hc-a2': 'CZ',
- name: 'Czech Republic',
- labelrank: '5',
- 'country-abbrev': 'Cz. Rep.',
- subregion: 'Eastern Europe',
- 'region-wb': 'Europe & Central Asia',
- 'iso-a3': 'CZE',
- 'iso-a2': 'CZ',
- 'woe-id': '23424810',
- continent: 'Europe',
- },
- geometry: {
- type: 'Polygon',
- coordinates: [
- [
- [5131, 3062],
- [5077, 3047],
- [5019, 2981],
- [5005, 2912],
- [4968, 2872],
- [4910, 2834],
- [4839, 2840],
- [4804, 2763],
- [4787, 2794],
- [4702, 2808],
- [4689, 2788],
- [4627, 2791],
- [4583, 2821],
- [4558, 2815],
- [4476, 2844],
- [4422, 2848],
- [4415, 2776],
- [4392, 2779],
- [4376, 2724],
- [4329, 2736],
- [4304, 2710],
- [4257, 2717],
- [4205, 2765],
- [4163, 2815],
- [4139, 2811],
- [4079, 2860],
- [4043, 2910],
- [4004, 2918],
- [3923, 3034],
- [3947, 3082],
- [3894, 3121],
- [3860, 3197],
- [3900, 3155],
- [3940, 3221],
- [4030, 3255],
- [4070, 3282],
- [4109, 3288],
- [4136, 3323],
- [4269, 3379],
- [4256, 3423],
- [4293, 3419],
- [4318, 3372],
- [4354, 3378],
- [4385, 3380],
- [4378, 3418],
- [4435, 3411],
- [4453, 3363],
- [4470, 3369],
- [4591, 3335],
- [4636, 3338],
- [4648, 3301],
- [4615, 3272],
- [4643, 3260],
- [4718, 3186],
- [4762, 3230],
- [4739, 3283],
- [4797, 3277],
- [4830, 3248],
- [4880, 3250],
- [4880, 3222],
- [4931, 3173],
- [4967, 3187],
- [5067, 3169],
- [5088, 3112],
- [5114, 3108],
- [5131, 3062],
- ],
- ],
- },
- },
- ],
-}
diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx
new file mode 100644
index 0000000..49ed164
--- /dev/null
+++ b/src/pages/dashboard.tsx
@@ -0,0 +1,72 @@
+import { Card, Col, Row, Statistic } from 'antd';
+import { startTransition, useDeferredValue, useEffect, useState } from 'react';
+
+import useIsMounted from '@/hooks/useIsMounted';
+import dashboardService from '../services/dashboard';
+
+type BrowserItem = { name: string; percent?: number };
+
+type DashboardData = {
+ cpu?: { cpu?: number; space?: number };
+ sales?: unknown[];
+ browser?: BrowserItem[];
+};
+
+export default function DashboardPage() {
+ const [data, setData] = useState({} as DashboardData);
+
+ const deferredData = useDeferredValue(data);
+
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ try {
+ const resp = await dashboardService.getDashboard();
+ if (isMounted()) startTransition(() => setData(resp || {}));
+ } catch (e) {
+ // ignore for demo
+ }
+ })();
+ }, [isMounted]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {(deferredData.browser || []).map((b) => (
+
+ {b.name}: {b.percent}%
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/dashboard/components/browser.js b/src/pages/dashboard/components/browser.js
deleted file mode 100644
index c17239f..0000000
--- a/src/pages/dashboard/components/browser.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Table, Tag } from 'antd'
-import { Color } from 'utils'
-import styles from './browser.less'
-
-const status = {
- 1: {
- color: Color.green,
- },
- 2: {
- color: Color.red,
- },
- 3: {
- color: Color.blue,
- },
- 4: {
- color: Color.yellow,
- },
-}
-
-function Browser({ data }) {
- const columns = [
- {
- title: 'name',
- dataIndex: 'name',
- className: styles.name,
- },
- {
- title: 'percent',
- dataIndex: 'percent',
- className: styles.percent,
- render: (text, it) => {text}% ,
- },
- ]
- return (
-
- )
-}
-
-Browser.propTypes = {
- data: PropTypes.array,
-}
-
-export default Browser
diff --git a/src/pages/dashboard/components/browser.less b/src/pages/dashboard/components/browser.less
deleted file mode 100644
index 631ad64..0000000
--- a/src/pages/dashboard/components/browser.less
+++ /dev/null
@@ -1,7 +0,0 @@
-.percent {
- text-align: right !important;
-}
-
-.name {
- text-align: left !important;
-}
diff --git a/src/pages/dashboard/components/comments.js b/src/pages/dashboard/components/comments.js
deleted file mode 100644
index 6403860..0000000
--- a/src/pages/dashboard/components/comments.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Table, Tag } from 'antd'
-import { Color } from 'utils'
-import styles from './comments.less'
-
-const status = {
- 1: {
- color: Color.green,
- text: 'APPROVED',
- },
- 2: {
- color: Color.yellow,
- text: 'PENDING',
- },
- 3: {
- color: Color.red,
- text: 'REJECTED',
- },
-}
-
-function Comments({ data }) {
- const columns = [
- {
- title: 'avatar',
- dataIndex: 'avatar',
- width: 48,
- className: styles.avatarcolumn,
- render: text => (
-
- ),
- },
- {
- title: 'content',
- dataIndex: 'content',
- render: (text, it) => (
-
-
{it.name}
-
{it.content}
-
- {status[it.status].text}
- {it.date}
-
-
- ),
- },
- ]
- return (
-
-
key < 3)}
- />
-
- )
-}
-
-Comments.propTypes = {
- data: PropTypes.array,
-}
-
-export default Comments
diff --git a/src/pages/dashboard/components/comments.less b/src/pages/dashboard/components/comments.less
deleted file mode 100644
index 98cbeaf..0000000
--- a/src/pages/dashboard/components/comments.less
+++ /dev/null
@@ -1,43 +0,0 @@
-@import '~themes/vars';
-
-.comments {
- :global .ant-table-thead > tr > th {
- background: #fff;
- border-bottom: solid 1px @border-color-base;
- }
-
- .avatar {
- width: 48px;
- height: 48px;
- background-position: center;
- background-size: cover;
- border-radius: 50%;
- background: #f8f8f8;
- display: inline-block;
- }
-
- .content {
- text-align: left;
- color: #757575;
- }
-
- .date {
- color: #a3a3a3;
- line-height: 30px;
- }
-
- .daterow {
- display: flex;
- justify-content: space-between;
- }
-
- .name {
- font-size: 14px;
- color: #474747;
- text-align: left;
- }
-
- .avatarcolumn {
- vertical-align: top;
- }
-}
diff --git a/src/pages/dashboard/components/completed.js b/src/pages/dashboard/components/completed.js
deleted file mode 100644
index 02ad904..0000000
--- a/src/pages/dashboard/components/completed.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import { Color } from 'utils'
-import {
- AreaChart,
- Area,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
-} from 'recharts'
-import styles from './completed.less'
-
-function Completed({ data }) {
- return (
-
-
TEAM TOTAL COMPLETED
-
-
- {
- const { payload } = prop
- return (
-
- {payload.map((item, key) => (
-
-
- {item.value}
-
- ))}
-
- )
- }}
- />
-
-
-
- {
- const list = content.payload.map((item, key) => (
-
-
- {`${item.name}:${item.value}`}
-
- ))
- return (
-
-
{content.label}
- {content.payload &&
}
-
- )
- }}
- />
-
-
-
-
-
- )
-}
-
-Completed.propTypes = {
- data: PropTypes.array,
-}
-
-export default Completed
diff --git a/src/pages/dashboard/components/completed.less b/src/pages/dashboard/components/completed.less
deleted file mode 100644
index b334092..0000000
--- a/src/pages/dashboard/components/completed.less
+++ /dev/null
@@ -1,49 +0,0 @@
-@import '~themes/vars';
-
-.sales {
- .title {
- margin-left: 32px;
- font-size: 16px;
- }
-}
-
-.radiusdot {
- width: 12px;
- height: 12px;
- margin-right: 8px;
- border-radius: 50%;
- display: inline-block;
-}
-
-.legend {
- text-align: right;
- color: #999;
- font-size: 14px;
-
- li {
- height: 48px;
- line-height: 48px;
- display: inline-block;
-
- & + li {
- margin-left: 24px;
- }
- }
-}
-
-.tooltip {
- background: #fff;
- padding: 20px;
- font-size: 14px;
-
- .tiptitle {
- font-weight: 700;
- font-size: 16px;
- margin-bottom: 8px;
- }
-
- .tipitem {
- height: 32px;
- line-height: 32px;
- }
-}
diff --git a/src/pages/dashboard/components/cpu.js b/src/pages/dashboard/components/cpu.js
deleted file mode 100644
index 3561555..0000000
--- a/src/pages/dashboard/components/cpu.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Color } from 'utils'
-import CountUp from 'react-countup'
-import {
- LineChart,
- Line,
- XAxis,
- YAxis,
- CartesianGrid,
- ResponsiveContainer,
-} from 'recharts'
-import styles from './cpu.less'
-
-const countUpProps = {
- start: 0,
- duration: 2.75,
- useEasing: true,
- useGrouping: true,
- separator: ',',
-}
-
-function Cpu({ usage = 0, space = 0, cpu = 0, data }) {
- return (
-
- )
-}
-
-Cpu.propTypes = {
- data: PropTypes.array,
- usage: PropTypes.number,
- space: PropTypes.number,
- cpu: PropTypes.number,
-}
-
-export default Cpu
diff --git a/src/pages/dashboard/components/cpu.less b/src/pages/dashboard/components/cpu.less
deleted file mode 100644
index d23163b..0000000
--- a/src/pages/dashboard/components/cpu.less
+++ /dev/null
@@ -1,40 +0,0 @@
-.cpu {
- .number {
- display: flex;
- height: 64px;
- justify-content: space-between;
- margin-bottom: 32px;
-
- .item {
- text-align: center;
- height: 64px;
- width: 100%;
- position: relative;
-
- & + .item {
- &::before {
- content: '';
- display: block;
- width: 1px;
- height: 40px;
- position: absolute;
- background: #f5f5f5;
- top: 12px;
- }
- }
-
- p {
- color: #757575;
-
- &:first-child {
- font-size: 16px;
- }
-
- &:last-child {
- font-size: 20px;
- font-weight: 700;
- }
- }
- }
- }
-}
diff --git a/src/pages/dashboard/components/index.js b/src/pages/dashboard/components/index.js
deleted file mode 100644
index a3d2993..0000000
--- a/src/pages/dashboard/components/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import NumberCard from './numberCard'
-import Quote from './quote'
-import Sales from './sales'
-import Weather from './weather'
-import RecentSales from './recentSales'
-import Comments from './comments'
-import Completed from './completed'
-import Browser from './browser'
-import Cpu from './cpu'
-import User from './user'
-
-export {
- NumberCard,
- Quote,
- Sales,
- Weather,
- RecentSales,
- Comments,
- Completed,
- Browser,
- Cpu,
- User,
-}
diff --git a/src/pages/dashboard/components/numberCard.js b/src/pages/dashboard/components/numberCard.js
deleted file mode 100644
index ce1936e..0000000
--- a/src/pages/dashboard/components/numberCard.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Card } from 'antd'
-import CountUp from 'react-countup'
-import iconMap from 'utils/iconMap'
-import styles from './numberCard.less'
-
-
-function NumberCard({ icon, color, title, number, countUp }) {
- return (
-
-
- {iconMap[icon]}
-
-
-
{title || 'No Title'}
-
-
-
-
-
- )
-}
-
-NumberCard.propTypes = {
- icon: PropTypes.string,
- color: PropTypes.string,
- title: PropTypes.string,
- number: PropTypes.number,
- countUp: PropTypes.object,
-}
-
-export default NumberCard
diff --git a/src/pages/dashboard/components/numberCard.less b/src/pages/dashboard/components/numberCard.less
deleted file mode 100644
index 237d3bf..0000000
--- a/src/pages/dashboard/components/numberCard.less
+++ /dev/null
@@ -1,33 +0,0 @@
-@import '~themes/vars';
-
-.numberCard {
- padding: 32px;
- margin-bottom: 24px;
- cursor: pointer;
-
- .iconWarp {
- font-size: 54px;
- float: left;
- }
-
- .content {
- width: 100%;
- padding-left: 78px;
-
- .title {
- line-height: 16px;
- font-size: 16px;
- margin-bottom: 8px;
- height: 16px;
- .text-overflow();
- }
-
- .number {
- line-height: 32px;
- font-size: 24px;
- height: 32px;
- .text-overflow();
- margin-bottom: 0;
- }
- }
-}
diff --git a/src/pages/dashboard/components/quote.js b/src/pages/dashboard/components/quote.js
deleted file mode 100644
index 751a65d..0000000
--- a/src/pages/dashboard/components/quote.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import styles from './quote.less'
-
-function Quote({ name, content, title, avatar }) {
- return (
-
- )
-}
-
-Quote.propTypes = {
- name: PropTypes.string,
- content: PropTypes.string,
- title: PropTypes.string,
- avatar: PropTypes.string,
-}
-
-export default Quote
diff --git a/src/pages/dashboard/components/quote.less b/src/pages/dashboard/components/quote.less
deleted file mode 100644
index 18e8d92..0000000
--- a/src/pages/dashboard/components/quote.less
+++ /dev/null
@@ -1,52 +0,0 @@
-@import '~themes/vars';
-
-.quote {
- color: #fff;
- height: 100%;
- width: 100%;
- padding: 24px;
- font-size: 16px;
- font-weight: 700;
-
- .inner {
- text-overflow: ellipsis;
- word-wrap: normal;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 4;
- overflow: hidden;
- text-indent: 24px;
- }
-
- .footer {
- position: relative;
- margin-top: 14px;
-
- .description {
- width: 100%;
-
- p {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- margin-right: 64px;
- text-align: right;
-
- &:last-child {
- font-weight: 100;
- }
- }
- }
-
- .avatar {
- width: 48px;
- height: 48px;
- background-position: center;
- background-size: cover;
- border-radius: 50%;
- position: absolute;
- right: 0;
- top: 0;
- }
- }
-}
diff --git a/src/pages/dashboard/components/recentSales.js b/src/pages/dashboard/components/recentSales.js
deleted file mode 100644
index bcfdac1..0000000
--- a/src/pages/dashboard/components/recentSales.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react'
-import dayjs from 'dayjs'
-import PropTypes from 'prop-types'
-import { Table, Tag } from 'antd'
-import { Color } from 'utils'
-import styles from './recentSales.less'
-
-const status = {
- 1: {
- color: Color.green,
- text: 'SALE',
- },
- 2: {
- color: Color.yellow,
- text: 'REJECT',
- },
- 3: {
- color: Color.red,
- text: 'TAX',
- },
- 4: {
- color: Color.blue,
- text: 'EXTENDED',
- },
-}
-
-function RecentSales({ data }) {
- const columns = [
- {
- title: 'NAME',
- dataIndex: 'name',
- },
- {
- title: 'STATUS',
- dataIndex: 'status',
- render: text => {status[text].text} ,
- },
- {
- title: 'DATE',
- dataIndex: 'date',
- render: text => dayjs(text).format('YYYY-MM-DD'),
- },
- {
- title: 'PRICE',
- dataIndex: 'price',
- render: (text, it) => (
- ${text}
- ),
- },
- ]
- return (
-
-
key < 5)}
- />
-
- )
-}
-
-RecentSales.propTypes = {
- data: PropTypes.array,
-}
-
-export default RecentSales
diff --git a/src/pages/dashboard/components/recentSales.less b/src/pages/dashboard/components/recentSales.less
deleted file mode 100644
index 9fcb7a2..0000000
--- a/src/pages/dashboard/components/recentSales.less
+++ /dev/null
@@ -1,8 +0,0 @@
-@import '~themes/vars';
-
-.recentsales {
- :global .ant-table-thead > tr > th {
- background: #fff;
- border-bottom: solid 1px @border-color-base;
- }
-}
diff --git a/src/pages/dashboard/components/sales.js b/src/pages/dashboard/components/sales.js
deleted file mode 100644
index 0c6e78d..0000000
--- a/src/pages/dashboard/components/sales.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import classnames from 'classnames'
-import { Color } from 'utils'
-import {
- LineChart,
- Line,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
-} from 'recharts'
-import styles from './sales.less'
-
-function Sales({ data }) {
- return (
-
-
Yearly Sales
-
-
- {
- const { payload } = prop
- return (
-
- {payload.map((item, key) => (
-
-
- {item.value}
-
- ))}
-
- )
- }}
- />
-
-
-
- {
- const list = content.payload.map((item, key) => (
-
-
- {`${item.name}:${item.value}`}
-
- ))
- return (
-
-
{content.label}
- {content.payload &&
}
-
- )
- }}
- />
-
-
-
-
-
-
- )
-}
-
-Sales.propTypes = {
- data: PropTypes.array,
-}
-
-export default Sales
diff --git a/src/pages/dashboard/components/sales.less b/src/pages/dashboard/components/sales.less
deleted file mode 100644
index 6d07f21..0000000
--- a/src/pages/dashboard/components/sales.less
+++ /dev/null
@@ -1,50 +0,0 @@
-@import '~themes/vars';
-
-.sales {
- overflow: hidden;
- .title {
- margin-left: 32px;
- font-size: 16px;
- }
-}
-
-.radiusdot {
- width: 12px;
- height: 12px;
- margin-right: 8px;
- border-radius: 50%;
- display: inline-block;
-}
-
-.legend {
- text-align: right;
- color: #999;
- font-size: 14px;
-
- li {
- height: 48px;
- line-height: 48px;
- display: inline-block;
-
- & + li {
- margin-left: 24px;
- }
- }
-}
-
-.tooltip {
- background: #fff;
- padding: 20px;
- font-size: 14px;
-
- .tiptitle {
- font-weight: 700;
- font-size: 16px;
- margin-bottom: 8px;
- }
-
- .tipitem {
- height: 32px;
- line-height: 32px;
- }
-}
diff --git a/src/pages/dashboard/components/user-background.png b/src/pages/dashboard/components/user-background.png
deleted file mode 100644
index 5f99e13..0000000
Binary files a/src/pages/dashboard/components/user-background.png and /dev/null differ
diff --git a/src/pages/dashboard/components/user.js b/src/pages/dashboard/components/user.js
deleted file mode 100644
index f5d2866..0000000
--- a/src/pages/dashboard/components/user.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Button, Avatar } from 'antd'
-import CountUp from 'react-countup'
-import { Color } from 'utils'
-import styles from './user.less'
-
-const countUpProps = {
- start: 0,
- duration: 2.75,
- useEasing: true,
- useGrouping: true,
- separator: ',',
-}
-
-function User({ avatar, username, sales = 0, sold = 0 }) {
- return (
-
-
-
-
-
- View Profile
-
-
-
- )
-}
-
-User.propTypes = {
- avatar: PropTypes.string,
- username: PropTypes.string,
- sales: PropTypes.number,
- sold: PropTypes.number,
-}
-
-export default User
diff --git a/src/pages/dashboard/components/user.less b/src/pages/dashboard/components/user.less
deleted file mode 100644
index 2babcc7..0000000
--- a/src/pages/dashboard/components/user.less
+++ /dev/null
@@ -1,88 +0,0 @@
-@import '~themes/vars';
-
-.user {
- .header {
- display: flex;
- justify-content: center;
- text-align: center;
- color: #fff;
- height: 200px;
- background-size: cover;
- align-items: center;
-
- .headerinner {
- z-index: 2;
- }
-
- &::after {
- content: '';
- background-image: url('./user-background.png');
- background-size: cover;
- position: absolute;
- width: 100%;
- height: 200px;
- left: 0;
- top: 0;
- opacity: 0.4;
- z-index: 1;
- }
-
- .name {
- font-size: 16px;
- margin-top: 8px;
- }
- }
-
- .number {
- display: flex;
- height: 116px;
- justify-content: space-between;
- border-bottom: solid 1px #f5f5f5;
-
- .item {
- text-align: center;
- height: 116px;
- width: 100%;
- position: relative;
- padding: 30px 0;
-
- & + .item {
- &::before {
- content: '';
- display: block;
- width: 1px;
- height: 116px;
- position: absolute;
- background: #f5f5f5;
- top: 0;
- }
- }
-
- p {
- color: #757575;
-
- &:first-child {
- font-size: 16px;
- }
-
- &:last-child {
- font-size: 20px;
- font-weight: 700;
- }
- }
- }
- }
-
- .footer {
- height: 116px;
- display: flex;
- justify-content: center;
- align-items: center;
-
- :global .ant-btn {
- color: @purple;
- border-color: @purple;
- padding: 6px 16px;
- }
- }
-}
diff --git a/src/pages/dashboard/components/weather.js b/src/pages/dashboard/components/weather.js
deleted file mode 100644
index 2cc8d31..0000000
--- a/src/pages/dashboard/components/weather.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Spin } from 'antd'
-import styles from './weather.less'
-
-function Weather({ city, icon, dateTime, temperature, name, loading }) {
- return (
-
-
-
-
-
{`${temperature}°`}
-
- {city},{dateTime}
-
-
-
-
- )
-}
-
-Weather.propTypes = {
- city: PropTypes.string,
- icon: PropTypes.string,
- dateTime: PropTypes.string,
- temperature: PropTypes.string,
- name: PropTypes.string,
- loading: PropTypes.bool,
-}
-
-export default Weather
diff --git a/src/pages/dashboard/components/weather.less b/src/pages/dashboard/components/weather.less
deleted file mode 100644
index a3f0e5b..0000000
--- a/src/pages/dashboard/components/weather.less
+++ /dev/null
@@ -1,48 +0,0 @@
-@import '~themes/vars';
-
-.weather {
- color: #fff;
- height: 204px;
- padding: 24px;
- justify-content: space-between;
- display: flex;
- font-size: 14px;
-
- .left {
- display: flex;
- flex-direction: column;
- width: 64px;
- padding-top: 55px;
-
- .icon {
- width: 64px;
- height: 64px;
- background-position: center;
- background-size: contain;
- }
-
- p {
- margin-top: 16px;
- }
- }
-
- .right {
- display: flex;
- flex-direction: column;
- width: 50%;
-
- .temperature {
- font-size: 36px;
- text-align: right;
- height: 64px;
- color: #fff;
- }
-
- .description {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: right;
- }
- }
-}
diff --git a/src/pages/dashboard/index.js b/src/pages/dashboard/index.js
deleted file mode 100644
index a216e11..0000000
--- a/src/pages/dashboard/index.js
+++ /dev/null
@@ -1,172 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'umi'
-import { Row, Col, Card } from 'antd'
-import { Color } from 'utils'
-import { Page, ScrollBar } from 'components'
-import {
- NumberCard,
- Quote,
- Sales,
- Weather,
- RecentSales,
- Comments,
- Completed,
- Browser,
- Cpu,
- User,
-} from './components'
-import styles from './index.less'
-import store from 'store'
-
-const bodyStyle = {
- styles: {
- body: {
- height: 432,
- background: '#fff',
- },
- },
-}
-
-@connect(({ app, dashboard, loading }) => ({
- dashboard,
- loading,
-}))
-class Dashboard extends PureComponent {
- render() {
- const userDetail = store.get('user')
- const { avatar, username } = userDetail
- const { dashboard, loading } = this.props
- const {
- weather,
- sales,
- quote,
- numbers,
- recentSales,
- comments,
- completed,
- browser,
- cpu,
- user,
- } = dashboard
-
- const numberCards = numbers.map((item, key) => (
-
-
-
- ))
-
- return (
-
-
- {numberCards}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
-}
-
-Dashboard.propTypes = {
- dashboard: PropTypes.object,
- loading: PropTypes.object,
-}
-
-export default Dashboard
diff --git a/src/pages/dashboard/index.less b/src/pages/dashboard/index.less
deleted file mode 100644
index 596b1c4..0000000
--- a/src/pages/dashboard/index.less
+++ /dev/null
@@ -1,27 +0,0 @@
-.dashboard {
- position: relative;
- :global {
- .ant-card {
- border-radius: 0;
- margin-bottom: 24px;
- &:hover {
- box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
- }
- }
- .ant-card-body {
- overflow-x: hidden;
- }
- }
-
- .weather {
- &:hover {
- box-shadow: 4px 4px 40px rgba(143, 201, 251, 0.6);
- }
- }
-
- .quote {
- &:hover {
- box-shadow: 4px 4px 40px rgba(246, 152, 153, 0.6);
- }
- }
-}
diff --git a/src/pages/dashboard/model.js b/src/pages/dashboard/model.js
deleted file mode 100644
index 8f9474c..0000000
--- a/src/pages/dashboard/model.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import { parse } from 'qs'
-import modelExtend from 'dva-model-extend'
-import api from 'api'
-import { pathToRegexp } from 'path-to-regexp'
-import { model } from 'utils/model'
-
-const { queryDashboard, queryWeather } = api
-const avatar = '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236.jpeg'
-
-export default modelExtend(model, {
- namespace: 'dashboard',
- state: {
- weather: {
- city: '深圳',
- temperature: '30',
- name: '晴',
- icon: '//cdn.antd-admin.zuiidea.com/sun.png',
- },
- sales: [],
- quote: {
- avatar,
- },
- numbers: [],
- recentSales: [],
- comments: [],
- completed: [],
- browser: [],
- cpu: {},
- user: {
- avatar,
- },
- },
- subscriptions: {
- setup({ dispatch, history }) {
- history.listen(({ pathname }) => {
- if (
- pathToRegexp('/dashboard').exec(pathname) ||
- pathToRegexp('/').exec(pathname)
- ) {
- dispatch({ type: 'query' })
- dispatch({ type: 'queryWeather' })
- }
- })
- },
- },
- effects: {
- *query({ payload }, { call, put }) {
- const data = yield call(queryDashboard, parse(payload))
- yield put({
- type: 'updateState',
- payload: data,
- })
- },
- *queryWeather({ payload = {} }, { call, put }) {
- payload.location = 'shenzhen'
- const result = yield call(queryWeather, payload)
- const { success } = result
- if (success) {
- const data = result.results[0]
- const weather = {
- city: data.location.name,
- temperature: data.now.temperature,
- name: data.now.text,
- icon: `//cdn.antd-admin.zuiidea.com/web/icons/3d_50/${data.now.code}.png`,
- }
- yield put({
- type: 'updateState',
- payload: {
- weather,
- },
- })
- }
- },
- },
-})
diff --git a/src/pages/dashboard/services/dashboard.js b/src/pages/dashboard/services/dashboard.js
deleted file mode 100644
index 71e9470..0000000
--- a/src/pages/dashboard/services/dashboard.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { request, config } from 'utils'
-
-const { api } = config
-const { dashboard } = api
-
-export function query(params) {
- return request({
- url: dashboard,
- method: 'get',
- data: params,
- })
-}
diff --git a/src/pages/dashboard/services/weather.js b/src/pages/dashboard/services/weather.js
deleted file mode 100644
index a87179e..0000000
--- a/src/pages/dashboard/services/weather.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { request, config } from 'utils'
-
-const { APIV1 } = config
-
-export function query(params) {
- params.key = 'i7sau1babuzwhycn'
- return request({
- url: `${APIV1}/weather/now.json`,
- method: 'get',
- data: params,
- })
-}
diff --git a/src/pages/editor/index.js b/src/pages/editor/index.js
deleted file mode 100644
index f65bb6a..0000000
--- a/src/pages/editor/index.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import { Component } from 'react'
-import { Editor, Page } from 'components'
-import { convertToRaw } from 'draft-js'
-import { Row, Col, Card } from 'antd'
-import draftToHtml from 'draftjs-to-html'
-import draftToMarkdown from 'draftjs-to-markdown'
-
-export default class EditorPage extends Component {
- constructor(props) {
- super(props)
- this.state = {
- editorContent: null,
- }
- }
-
- onEditorStateChange = editorContent => {
- this.setState({
- editorContent,
- })
- }
-
- render() {
- const { editorContent } = this.state
- const colProps = {
- lg: 12,
- md: 24,
- style: {
- marginBottom: 32,
- }
- }
- const textareaStyle = {
- minHeight: 496,
- width: '100%',
- background: '#f7f7f7',
- borderColor: '#F1F1F1',
- padding: '16px 8px'
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
-}
diff --git a/src/pages/guide.tsx b/src/pages/guide.tsx
new file mode 100644
index 0000000..8addea6
--- /dev/null
+++ b/src/pages/guide.tsx
@@ -0,0 +1,192 @@
+import { Button, Card, Divider, Typography } from 'antd';
+import { setLocale, useIntl } from 'umi';
+
+const { Title, Paragraph, Text } = Typography;
+
+export default function HomePage() {
+ const intl = useIntl();
+ return (
+
+
+ {intl.formatMessage({ id: 'welcome' }) || 'Welcome'}
+
+
+
+
+ 快速开始
+
+
+ 运行开发环境:
+
+ npm run dev
+
+
+
+
+
+
+ 添加页面
+
+
+ 在 src/pages 下新建一个文件 (例如{' '}
+ src/pages/hello.tsx ),Umi 会自动把它注册为路由。
+
+
+ 示例页面代码:
+
+ {`import React from 'react';
+export default function Hello() {
+ return Hello 页面
;
+}`}
+
+
+
+
+
+
+ 多语言 (i18n)
+
+
+ 本项目使用 Umi 的国际化支持,翻译文件位于{' '}
+ src/locales (例如 zh-CN.json 、
+ en-US.json )。
+
+
+ {intl.formatMessage({ id: 'guide.i18n.example' })}
+
+
+ {`// src/locales/zh-CN.json
+{
+ "welcome": "欢迎使用本项目"
+}
+
+// 页面中使用
+import { useIntl } from 'umi';
+const intl = useIntl();
+intl.formatMessage({ id: 'welcome' });`}
+
+
+ {intl.formatMessage({ id: 'guide.i18n.usage' })}{' '}
+ setLocale('zh-CN', true)}>
+ 中文
+
+ setLocale('en-US', true)}
+ >
+ English
+
+
+
+
+
+
+ 引入 ECharts
+
+ 安装依赖(项目根目录):
+
+ npm install echarts --save
+
+ 在页面中使用:
+
+ {`import React, { useEffect, useRef } from 'react';
+import * as echarts from 'echarts';
+
+export default function ChartPage() {
+ const ref = useRef(null);
+ useEffect(() => {
+ if (!ref.current) return;
+ const chart = echarts.init(ref.current);
+ chart.setOption({ xAxis: { type: 'category', data: ['a','b','c'] }, yAxis: { type: 'value' }, series: [{ data: [5,20,36], type: 'bar' }] });
+ return () => chart.dispose();
+ }, []);
+ return
;
+}`}
+
+
+
+
+
+ 图标优化
+
+
+ 项目中的菜单图标已抽取到组件:
+ src/components/IconMap.tsx ,PrimaryLayout
+ 已从此处引入并使用统一的 ICON_MAP。
+
+ 使用示例:
+
+ {`import { ICON_MAP, Icon } from '@/components/IconMap';
+const icon = ICON_MAP['dashboard']; // 或者 `}
+
+
+
+
+
+ 测试(Testing)
+
+
+ 本项目使用 Jest 和 @testing-library/react
+ 进行单元测试。测试文件通常放在被测模块同目录,文件名以{' '}
+ .test.ts 或 .test.tsx 结尾。
+
+
+
+ 运行测试命令:
+
+ {`pnpm install
+pnpm test
+pnpm test:watch`}
+
+
+
+
+ 测试函数示例(放在 src/utils ):
+
+
+ {`// src/utils/sum.ts
+export function sum(a: number, b: number) { return a + b }
+
+// src/utils/sum.test.ts
+import { sum } from './sum'
+
+test('sum adds numbers', () => {
+ expect(sum(1, 2)).toBe(3)
+})`}
+
+
+ 测试组件示例(使用 @testing-library/react):
+
+ {`// src/components/DropOption.test.tsx
+import { render, screen, fireEvent } from '@testing-library/react'
+import DropOption from './DropOption'
+
+test('clicking menu item calls callback', () => {
+ const onMenuClick = jest.fn()
+ render( )
+ fireEvent.click(screen.getByText('One'))
+ expect(onMenuClick).toHaveBeenCalledWith('one')
+})`}
+
+
+ 小贴士:
+
+
+ 使用 `jest.fn()` 创建 mock 回调;使用 `jest.mock()` 模拟模块。
+
+
+ 已在 src/setupTests.ts 中引入
+ `@testing-library/jest-dom`,可使用额外断言。
+
+ 需要模拟网络请求时,可 mock 全局 `fetch` 或使用 `msw`。
+
+
+
+
+
+ 更多帮助请查看项目 README 或联系维护者。
+
+
+ );
+}
diff --git a/src/pages/index.js b/src/pages/index.js
deleted file mode 100644
index 4ab73b9..0000000
--- a/src/pages/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React, { PureComponent } from 'react'
-import { Redirect } from 'umi'
-import { i18n } from '@lingui/core'
-
-class Index extends PureComponent {
- render() {
- return
- }
-}
-
-export default Index
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..e0ad9f6
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,13 @@
+import { useEffect } from 'react';
+import { history } from 'umi';
+
+export default function RootRedirect() {
+ useEffect(() => {
+ try {
+ history.replace('/dashboard');
+ } catch (e) {
+ window.location.href = '/dashboard';
+ }
+ }, []);
+ return null;
+}
diff --git a/src/pages/login/index.js b/src/pages/login/index.js
deleted file mode 100644
index 9f0688b..0000000
--- a/src/pages/login/index.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import React, { PureComponent, Fragment } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'umi'
-import { Button, Row, Input, Form } from 'antd'
-import { GlobalFooter } from 'components'
-import { GithubOutlined } from '@ant-design/icons'
-import { i18n } from '@lingui/core'
-import { setLocale } from 'utils'
-import config from 'utils/config'
-
-import styles from './index.less'
-
-const FormItem = Form.Item
-
-@connect(({ loading, dispatch }) => ({ loading, dispatch }))
-class Login extends PureComponent {
-
- render() {
- const { dispatch, loading } = this.props
-
- const handleOk = values => {
- dispatch({ type: 'login/login', payload: values })
- }
- let footerLinks = [
- {
- key: 'github',
- title: ,
- href: 'https://github.com/zuiidea/antd-admin',
- blankTarget: true,
- },
- ]
-
- if (config.i18n) {
- footerLinks = footerLinks.concat(
- config.i18n.languages.map(item => ({
- key: item.key,
- title: (
- {item.title}
- ),
- }))
- )
- }
-
- return (
-
-
-
-
-
{config.siteName}
-
-
-
-
-
-
-
- )
- }
-}
-
-Login.propTypes = {
- form: PropTypes.object,
- dispatch: PropTypes.func,
- loading: PropTypes.object,
-}
-
-export default Login
diff --git a/src/pages/login/index.less b/src/pages/login/index.less
deleted file mode 100644
index d7642d6..0000000
--- a/src/pages/login/index.less
+++ /dev/null
@@ -1,60 +0,0 @@
-@import '~themes/vars';
-
-.form {
- position: absolute;
- top: 45%;
- left: 50%;
- margin: -160px 0 0 -160px;
- width: 320px;
- height: 320px;
- padding: 36px;
- box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
-
- button {
- width: 100%;
- }
-
- p {
- color: rgb(204, 204, 204);
- text-align: center;
- margin-top: 16px;
- font-size: 12px;
- display: flex;
- justify-content: space-between;
- }
-}
-
-.logo {
- text-align: center;
- cursor: pointer;
- margin-bottom: 24px;
- display: flex;
- justify-content: center;
- align-items: center;
-
- img {
- width: 40px;
- margin-right: 8px;
- }
-
- span {
- vertical-align: text-bottom;
- font-size: 16px;
- text-transform: uppercase;
- display: inline-block;
- font-weight: 700;
- color: @primary-color;
- .text-gradient();
- }
-}
-
-.ant-spin-container,
-.ant-spin-nested-loading {
- height: 100%;
-}
-
-.footer {
- position: absolute;
- width: 100%;
- bottom: 0;
-}
diff --git a/src/pages/login/index.module.less b/src/pages/login/index.module.less
new file mode 100644
index 0000000..19b099e
--- /dev/null
+++ b/src/pages/login/index.module.less
@@ -0,0 +1,83 @@
+/* Styles adapted from reference login module */
+.page {
+ position: relative;
+ min-height: 100vh;
+}
+
+.form {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ padding: 36px;
+ box-shadow: 0 0 40px rgba(0, 0, 0, 0.08);
+ border-radius: 12px;
+ width: 360px;
+ background: #fff;
+
+ h2 {
+ margin-bottom: 4px;
+ }
+
+ p {
+ color: #64748b;
+ margin-bottom: 18px;
+ }
+
+ button {
+ width: 100%;
+ margin-top: 12px;
+ }
+
+ .remember {
+ float: left;
+ }
+}
+
+.logo {
+ height: 40px;
+ line-height: 40px;
+ cursor: pointer;
+ margin-bottom: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+
+ img {
+ height: 40px;
+ width: 40px;
+ }
+
+ span {
+ vertical-align: middle;
+ font-size: 16px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: #0f172a;
+ display: inline-block;
+ }
+}
+
+.toolbar {
+ position: absolute;
+ right: 16px;
+ top: 16px;
+ display: flex;
+ gap: 8px;
+ z-index: 20;
+}
+
+@media (max-width: 576px) {
+ .form {
+ width: calc(100vw - 32px);
+ padding: 24px;
+ }
+}
+
+.footer {
+ position: absolute;
+ width: 100%;
+ bottom: 0;
+}
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx
new file mode 100644
index 0000000..346c521
--- /dev/null
+++ b/src/pages/login/index.tsx
@@ -0,0 +1,109 @@
+import { LockOutlined, UserOutlined } from '@ant-design/icons';
+import { Button, Checkbox, Form, Input, message } from 'antd';
+import { history, useIntl } from 'umi';
+
+import adminService from '@/services/admin';
+import useGlobalStore from '@/store/useGlobalStore';
+import config from '@/utils/config';
+import styles from './index.module.less';
+
+export default function Login() {
+ const intl = useIntl();
+ const [form] = Form.useForm();
+
+ type LoginForm = { username?: string; adminname?: string; password?: string };
+
+ const onFinish = (values: LoginForm) => {
+ (async () => {
+ try {
+ const creds = {
+ adminname: String(values.username ?? values.adminname ?? ''),
+ password: String(values.password ?? ''),
+ };
+ await adminService.login(creds);
+ message.success(intl.formatMessage({ id: 'login.success' }));
+ useGlobalStore.getState().login();
+
+ const admin = (await adminService.currentAdmin()) as
+ | import('@/types/admin').Admin
+ | undefined;
+ if (admin) {
+ useGlobalStore.getState().setCurrentUser(admin);
+ }
+
+ history.push('/dashboard');
+ } catch (e: any) {
+ console.error(e);
+ }
+ })();
+ };
+
+ return (
+
+
+
+
+
{config.siteName}
+
+
+
+ }
+ autoComplete="username"
+ placeholder={intl.formatMessage({ id: 'login.username' })}
+ />
+
+
+
+ }
+ autoComplete="current-password"
+ placeholder={intl.formatMessage({ id: 'login.password' })}
+ />
+
+
+
+ {intl.formatMessage({ id: 'login.remember' })}
+
+
+
+ use guest/guest or admin/admin login
+
+
+
+ {intl.formatMessage({ id: 'login.submit' })}
+
+
+
+
+ );
+}
diff --git a/src/pages/login/model.js b/src/pages/login/model.js
deleted file mode 100644
index e779e86..0000000
--- a/src/pages/login/model.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { history } from 'umi'
-import { pathToRegexp } from 'path-to-regexp'
-import api from 'api'
-
-const { loginUser } = api
-
-const loginModel = {
- namespace: 'login',
-
- state: {},
- // subscriptions: {
- // setup({ dispatch, history }) {
- // history.listen(location => {
- // if (pathToRegexp('/login').exec(location.pathname)) {
- // }
- // })
- // },
- // },
- effects: {
- *login({ payload }, { put, call, select }) {
- const data = yield call(loginUser, payload)
- const { locationQuery } = yield select(_ => _.app)
- if (data.success) {
- const { from } = locationQuery
- yield put({ type: 'app/query' })
- if (!pathToRegexp('/login').exec(from)) {
- if (['', '/'].includes(from)) history.push('/dashboard')
- else history.push(from)
- } else {
- history.push('/dashboard')
- }
- } else {
- throw data
- }
- },
- },
-}
-
-export default loginModel
diff --git a/src/pages/post/components/List.js b/src/pages/post/components/List.js
deleted file mode 100644
index 99411d1..0000000
--- a/src/pages/post/components/List.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, { PureComponent } from 'react'
-import { Table, Avatar } from 'antd'
-import { i18n } from '@lingui/core'
-import { Ellipsis } from 'components'
-import styles from './List.less'
-
-class List extends PureComponent {
- render() {
- const { ...tableProps } = this.props
- const columns = [
- {
- title: i18n._('Image'),
- dataIndex: 'image',
- render: text => ,
- },
- {
- title: i18n._('Title'),
- dataIndex: 'title',
- render: text => (
-
- {text}
-
- ),
- },
- {
- title: i18n._('Author'),
- dataIndex: 'author',
- },
- {
- title: i18n._('Categories'),
- dataIndex: 'categories',
- },
- {
- title: i18n._('Tags'),
- dataIndex: 'tags',
- },
- {
- title: i18n._('Visibility'),
- dataIndex: 'visibility',
- },
- {
- title: i18n._('Comments'),
- dataIndex: 'comments',
- },
- {
- title: i18n._('Views'),
- dataIndex: 'views',
- },
- {
- title: i18n._('Publish Date'),
- dataIndex: 'date',
- },
- ]
-
- return (
- i18n._('Total {total} Items', { total }),
- }}
- bordered
- scroll={{ x: 1200 }}
- className={styles.table}
- columns={columns}
- simple
- rowKey={record => record.id}
- />
- )
- }
-}
-
-export default List
diff --git a/src/pages/post/components/List.less b/src/pages/post/components/List.less
deleted file mode 100644
index 6d35765..0000000
--- a/src/pages/post/components/List.less
+++ /dev/null
@@ -1,7 +0,0 @@
-.table {
- :global {
- .ant-table td {
- white-space: nowrap;
- }
- }
-}
diff --git a/src/pages/post/index.js b/src/pages/post/index.js
deleted file mode 100644
index b192ccf..0000000
--- a/src/pages/post/index.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'umi'
-import { Tabs } from 'antd'
-import { history } from 'umi'
-import { stringify } from 'qs'
-import { i18n } from '@lingui/core'
-import { Page } from 'components'
-import List from './components/List'
-
-// use `items` prop instead of Tabs.TabPane
-
-const EnumPostStatus = {
- UNPUBLISH: 1,
- PUBLISHED: 2,
-}
-
-@connect(({ post, loading }) => ({ post, loading }))
-class Post extends PureComponent {
- handleTabClick = key => {
- const { pathname } = this.props.location
-
- history.push({
- pathname,
- search: stringify({
- status: key,
- }),
- })
- }
-
- get listProps() {
- const { post, loading, location } = this.props
- const { list, pagination } = post
- const { query, pathname } = location
-
- return {
- pagination,
- dataSource: list,
- loading: loading.effects['post/query'],
- onChange(page) {
- history.push({
- pathname,
- search: stringify({
- ...query,
- page: page.current,
- pageSize: page.pageSize,
- }),
- })
- },
- }
- }
-
- render() {
- const { location } = this.props
- const { query } = location
-
- return (
-
- ,
- },
- {
- key: String(EnumPostStatus.UNPUBLISH),
- label: i18n._('Unpublished'),
- children:
,
- },
- ]}
- />
-
- )
- }
-}
-
-Post.propTypes = {
- post: PropTypes.object,
- loading: PropTypes.object,
- location: PropTypes.object,
- dispatch: PropTypes.func,
-}
-
-export default Post
diff --git a/src/pages/post/model.js b/src/pages/post/model.js
deleted file mode 100644
index a3df06c..0000000
--- a/src/pages/post/model.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import modelExtend from 'dva-model-extend'
-import api from 'api'
-import { pathToRegexp } from 'path-to-regexp'
-import { pageModel } from 'utils/model'
-
-const { queryPostList } = api
-
-export default modelExtend(pageModel, {
- namespace: 'post',
-
- subscriptions: {
- setup({ dispatch, history }) {
- history.listen(location => {
- if (pathToRegexp('/post').exec(location.pathname)) {
- dispatch({
- type: 'query',
- payload: {
- status: 2,
- ...location.query,
- },
- })
- }
- })
- },
- },
-
- effects: {
- *query({ payload }, { call, put }) {
- const data = yield call(queryPostList, payload)
- if (data.success) {
- yield put({
- type: 'querySuccess',
- payload: {
- list: data.data,
- pagination: {
- current: Number(payload.page) || 1,
- pageSize: Number(payload.pageSize) || 10,
- total: data.total,
- },
- },
- })
- } else {
- throw data
- }
- },
- },
-})
diff --git a/src/pages/request/index.js b/src/pages/request/index.js
deleted file mode 100644
index 4c20582..0000000
--- a/src/pages/request/index.js
+++ /dev/null
@@ -1,261 +0,0 @@
-import React from 'react'
-import { request } from 'utils'
-import { apiPrefix } from 'utils/config'
-import { Row, Col, Select, Form, Input, Button, List, Tag, Checkbox } from 'antd'
-import classnames from 'classnames'
-import { CloseOutlined } from '@ant-design/icons'
-import { i18n } from '@lingui/core'
-import api from '@/services/api'
-import { Page } from 'components'
-
-import styles from './index.less'
-
-const { Option } = Select
-const InputGroup = Input.Group
-const methods = ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']
-
-const methodTagColor = {
- GET: 'green',
- POST: 'orange',
- DELETE: 'red',
- PUT: 'geekblue',
-}
-
-const requests = Object.values(api).map(item => {
- let url = apiPrefix + item
- let method = 'GET'
- const paramsArray = item.split(' ')
- if (paramsArray.length === 2) {
- method = paramsArray[0]
- url = apiPrefix + paramsArray[1]
- }
- return {
- method,
- url,
- }
-})
-
-let uuid = 2
-class RequestPage extends React.Component {
- formRef = React.createRef()
- constructor(props) {
- super(props)
- this.state = {
- method: 'GET',
- url: '/api/v1/routes',
- keys: [1],
- result: null,
- visible: true,
- }
- }
-
- handleRequest = () => {
- const { method, url } = this.state
-
- this.formRef.current.validateFields()
- .then(values => {
- // values: { check[1]: true, key[1]: 'username', value[1]: 'admin' }
-
- const params = {}
- for (let i in values) {
- if (i.startsWith('check')) {
- const index = i.match(/check\[(\d+)\]/)[1]
- const key = values[`key[${index}]`]
- params[key] = values[`value[${index}]`]
- }
- }
-
- request({ method, url, data: params }).then(data => {
- this.setState({
- result: JSON.stringify(data),
- })
- })
- })
- .catch(errorInfo => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.error(errorInfo)
- }
- /*
- errorInfo:
- {
- values: {
- username: 'username',
- password: 'password',
- },
- errorFields: [
- { password: ['username'], errors: ['Please input your Password!'] },
- ],
- outOfDate: false,
- }
- */
- })
- }
-
- handleClickListItem = ({ method, url }) => {
- this.setState({
- method,
- url,
- keys: [uuid++],
- result: null,
- })
- }
-
- handleInputChange = e => {
- this.setState({
- url: e.target.value,
- })
- }
-
- handleSelectChange = method => {
- this.setState({
- method,
- })
- }
-
- handleAddField = () => {
- const { keys } = this.state
- const nextKeys = keys.concat(uuid)
- uuid++
- this.setState({
- keys: nextKeys,
- })
- }
-
- handleRemoveField = key => {
- const { keys } = this.state
- this.setState({
- keys: keys.filter(item => item !== key),
- })
- }
-
- handleVisible = () => {
- this.setState({
- visible: !this.state.visible,
- })
- }
-
- render() {
- const { result, url, method, keys, visible } = this.state
-
- return (
-
-
-
- (
-
-
-
- {item.method}
-
-
- {item.url}
-
- )}
- />
-
-
-
-
-
- {methods.map(item => (
-
- {item}
-
- ))}
-
-
-
- {i18n._('Params')}
-
-
-
-
- {i18n._('Send')}
-
-
-
- {result}
-
-
-
- )
- }
-}
-
-export default RequestPage
diff --git a/src/pages/request/index.less b/src/pages/request/index.less
deleted file mode 100644
index a878f39..0000000
--- a/src/pages/request/index.less
+++ /dev/null
@@ -1,39 +0,0 @@
-@import '~themes/vars';
-
-.result {
- height: 600px;
- width: 100%;
- background: @hover-color;
- border-color: #ddd;
- padding: 16px;
- margin-top: 16px;
- word-break: break-word;
- line-height: 2;
- overflow: scroll;
-}
-
-.requestList {
- padding-right: 24px;
- margin-bottom: 24px;
- .listItem {
- cursor: pointer;
- padding-left: 8px;
- &.lstItemActive {
- background-color: @hover-color;
- }
- .background-hover();
- }
-}
-
-.paramsBlock {
- overflow: visible;
- opacity: 1;
- height: auto;
- transition: opacity 0.3s;
- &.hideParams {
- width: 0;
- height: 0;
- opacity: 0;
- overflow: hidden;
- }
-}
diff --git a/src/pages/sys/menu/components/Filter.tsx b/src/pages/sys/menu/components/Filter.tsx
new file mode 100644
index 0000000..d036a26
--- /dev/null
+++ b/src/pages/sys/menu/components/Filter.tsx
@@ -0,0 +1,29 @@
+import { Button, Space } from 'antd';
+
+type Props = {
+ selectedCount: number;
+ onAdd: () => void;
+ onSort: () => void;
+ onToggleSelectedStatus: () => void;
+};
+
+export default function Filter({
+ selectedCount,
+ onAdd,
+ onSort,
+ onToggleSelectedStatus,
+}: Props) {
+ return (
+
+ 新增模块
+ 模块排序
+
+ 启用/禁用选中项{selectedCount > 0 ? ` (${selectedCount})` : ''}
+
+
+ );
+}
diff --git a/src/pages/sys/menu/components/List.tsx b/src/pages/sys/menu/components/List.tsx
new file mode 100644
index 0000000..116b522
--- /dev/null
+++ b/src/pages/sys/menu/components/List.tsx
@@ -0,0 +1,80 @@
+import DropOption from '@/components/DropOption';
+import type { MenuItem } from '@/types/menu';
+import type { TableColumnsType } from 'antd';
+import { Space, Table, Tag } from 'antd';
+import React from 'react';
+
+type Props = {
+ dataSource: MenuItem[];
+ selectedRowKeys: React.Key[];
+ onSelectChange: (keys: React.Key[]) => void;
+ onEditItem: (item: MenuItem) => void;
+ onDeleteItem: (id: number) => void;
+};
+
+export default function MenuList({
+ dataSource,
+ selectedRowKeys,
+ onSelectChange,
+ onEditItem,
+ onDeleteItem,
+}: Props) {
+ const normalizeChildren = (items?: MenuItem[]): MenuItem[] | undefined => {
+ if (!items) return undefined;
+ const next = items.map((it) => ({
+ ...it,
+ children: normalizeChildren(it.children),
+ }));
+ return next && next.length ? next : undefined;
+ };
+ const columns: TableColumnsType = [
+ { title: 'No.', dataIndex: 'id', width: 90 },
+ { title: '名称', dataIndex: 'title', width: 180 },
+ { title: '图标', dataIndex: 'icon', width: 180, render: (v) => v || '-' },
+ { title: '路径', dataIndex: 'route', width: 260 },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ width: 120,
+ render: (status: number) =>
+ status === 1 ? 已启用 : 已禁用 ,
+ },
+ {
+ title: '操作',
+ key: 'action',
+ width: 170,
+ render: (_, record) => {
+ const menuOptions = [
+ { key: 'edit', label: 修改 },
+ { key: 'delete', label: 删除 },
+ ];
+
+ const handleMenuClick = (key: string) => {
+ if (key === 'edit') onEditItem(record);
+ if (key === 'delete') onDeleteItem(record.id);
+ };
+
+ return (
+
+
+
+ );
+ },
+ },
+ ];
+
+ return (
+
+ bordered
+ rowKey="id"
+ columns={columns}
+ dataSource={(normalizeChildren(dataSource) || []) as MenuItem[]}
+ pagination={false}
+ rowSelection={{ selectedRowKeys, onChange: onSelectChange }}
+ scroll={{ x: 'max-content' }}
+ />
+ );
+}
diff --git a/src/pages/sys/menu/components/Modal.tsx b/src/pages/sys/menu/components/Modal.tsx
new file mode 100644
index 0000000..eb3be66
--- /dev/null
+++ b/src/pages/sys/menu/components/Modal.tsx
@@ -0,0 +1,127 @@
+import type { MenuFormValues, MenuItem } from '@/types/menu';
+import {
+ Form,
+ Input,
+ InputNumber,
+ Modal,
+ Radio,
+ Select,
+ TreeSelect,
+} from 'antd';
+import { useEffect } from 'react';
+
+type Props = {
+ open: boolean;
+ type: 'create' | 'update';
+ item?: MenuItem;
+ parentOptions: Array<{
+ title: string;
+ value: number;
+ children?: Array<{ title: string; value: number }>;
+ }>;
+ onOk: (values: MenuFormValues) => void;
+ onCancel: () => void;
+};
+
+const privilegeOptions = [
+ { label: '增', value: 'create' },
+ { label: '删', value: 'delete' },
+ { label: '改', value: 'update' },
+ { label: '查', value: 'query' },
+];
+
+export default function MenuModal({
+ open,
+ type,
+ item,
+ parentOptions,
+ onOk,
+ onCancel,
+}: Props) {
+ const [form] = Form.useForm();
+
+ useEffect(() => {
+ if (!open) return;
+ form.setFieldsValue({
+ title: item?.title || '',
+ icon: item?.icon || '',
+ route: item?.route || '',
+ status: item?.status ?? 1,
+ parentId: item?.parentId ?? null,
+ privilege: item?.privilege || [],
+ remark: item?.remark || '',
+ sort: item?.sort ?? 1,
+ });
+ }, [open, item, form]);
+
+ const handleOk = async () => {
+ const values = await form.validateFields();
+ onOk(values);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/sys/menu/components/SortModal.tsx b/src/pages/sys/menu/components/SortModal.tsx
new file mode 100644
index 0000000..65e1021
--- /dev/null
+++ b/src/pages/sys/menu/components/SortModal.tsx
@@ -0,0 +1,212 @@
+import type { MenuItem } from '@/types/menu';
+import { Button, Modal, Space, Table } from 'antd';
+import { startTransition, useDeferredValue, useEffect, useState } from 'react';
+
+type SortRow = {
+ id: number;
+ title: string;
+ route?: string;
+ sort: number;
+ children?: SortRow[];
+};
+
+type Props = {
+ open: boolean;
+ menus: MenuItem[];
+ onCancel: () => void;
+ // now returns the updated tree with new sort order
+ onConfirm: (updatedMenus: MenuItem[]) => void;
+};
+
+function toRows(menus: MenuItem[]): SortRow[] {
+ return [...menus]
+ .sort((a, b) => a.sort - b.sort)
+ .map((m) => ({
+ id: m.id,
+ title: m.title,
+ route: m.route || '/',
+ sort: m.sort || 0,
+ children: m.children ? toRows(m.children) : undefined,
+ }));
+}
+
+export default function SortModal({ open, menus, onCancel, onConfirm }: Props) {
+ const [rows, setRows] = useState([]);
+ useEffect(() => {
+ if (!open) return;
+ startTransition(() => setRows(toRows(menus)));
+ }, [open, menus]);
+
+ const deferredRows = useDeferredValue(rows);
+
+ const move = (list: SortRow[], idx: number, step: -1 | 1) => {
+ const next = [...list];
+ const target = idx + step;
+ if (target < 0 || target >= next.length) return list;
+ const t = next[idx];
+ next[idx] = next[target];
+ next[target] = t;
+ return next.map((r, i) => ({ ...r, sort: i + 1 }));
+ };
+
+ const moveRoot = (idx: number, step: -1 | 1) =>
+ setRows((prev) => move(prev, idx, step));
+
+ const moveChild = (rootIdx: number, childIdx: number, step: -1 | 1) => {
+ setRows((prev) => {
+ const next = [...prev];
+ const root = {
+ ...next[rootIdx],
+ children: prev[rootIdx].children ? [...prev[rootIdx].children] : [],
+ };
+ root.children = move(root.children || [], childIdx, step);
+ next[rootIdx] = root;
+ return next;
+ });
+ };
+
+ const buildMenuTree = (rows: SortRow[]): MenuItem[] => {
+ return rows.map(
+ (r) =>
+ ({
+ id: r.id,
+ title: r.title,
+ route: r.route || '/',
+ sort: r.sort,
+ children: r.children ? buildMenuTree(r.children) : undefined,
+ } as MenuItem),
+ );
+ };
+
+ return (
+ onConfirm(buildMenuTree(rows))}
+ destroyOnHidden
+ width={900}
+ styles={{
+ body: {
+ maxHeight: '60vh',
+ overflowY: 'auto',
+ },
+ }}
+ >
+
+ {deferredRows.map((r, idx) => (
+
+
+
+
+ {r.title}
+
+
+ {r.route}
+
+
+
+
+ moveRoot(idx, -1)}
+ disabled={idx === 0}
+ >
+ 上移
+
+ moveRoot(idx, 1)}
+ disabled={idx === rows.length - 1}
+ >
+ 下移
+
+
+
+
+ {r.children && r.children.length ? (
+
+
a.sort - b.sort)}
+ columns={[
+ { title: '排序', dataIndex: 'sort', width: 80 },
+ { title: '名称', dataIndex: 'title' },
+ { title: '路径', dataIndex: 'route' },
+ {
+ title: '操作',
+ width: 140,
+ render: (_, __, childIdx: number) => (
+
+ moveChild(idx, childIdx, -1)}
+ disabled={childIdx === 0}
+ >
+ 上移
+
+ moveChild(idx, childIdx, 1)}
+ disabled={
+ !(r.children && r.children.length) ||
+ childIdx ===
+ (r.children ? r.children.length - 1 : 0)
+ }
+ >
+ 下移
+
+
+ ),
+ },
+ ]}
+ style={{ minWidth: 600 }}
+ scroll={{ x: 'max-content' }}
+ />
+
+ ) : null}
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/sys/menu/index.tsx b/src/pages/sys/menu/index.tsx
new file mode 100644
index 0000000..c38e75d
--- /dev/null
+++ b/src/pages/sys/menu/index.tsx
@@ -0,0 +1,249 @@
+import { Card, message } from 'antd';
+import React, { useMemo, useState } from 'react';
+
+import useIsMounted from '@/hooks/useIsMounted';
+import * as menuService from '@/services/menu';
+import type { MenuFormValues, MenuItem } from '@/types/menu';
+import Filter from './components/Filter';
+import MenuList from './components/List';
+import MenuModal from './components/Modal';
+import SortModal from './components/SortModal';
+
+function mapTree(
+ items: MenuItem[],
+ fn: (item: MenuItem) => MenuItem,
+): MenuItem[] {
+ return items.map((item) => {
+ const next = fn(item);
+ if (!item.children?.length) return next;
+ return { ...next, children: mapTree(item.children, fn) };
+ });
+}
+
+function normalizeChildren(items?: MenuItem[]): MenuItem[] {
+ if (!items || !items.length) return [];
+ return items
+ .map((it) => ({
+ ...it,
+ children: normalizeChildren(it.children) || undefined,
+ }))
+ .map((it) => ({
+ ...it,
+ children: it.children && it.children.length ? it.children : undefined,
+ }));
+}
+
+function removeNode(items: MenuItem[], id: number): MenuItem[] {
+ return items
+ .filter((item) => item.id !== id)
+ .map((item) => ({
+ ...item,
+ children: item.children ? removeNode(item.children, id) : undefined,
+ }));
+}
+
+function updateNode(
+ items: MenuItem[],
+ id: number,
+ updater: (node: MenuItem) => MenuItem,
+): MenuItem[] {
+ return items.map((item) => {
+ if (item.id === id) return updater(item);
+ if (!item.children?.length) return item;
+ return { ...item, children: updateNode(item.children, id, updater) };
+ });
+}
+
+function addNode(
+ items: MenuItem[],
+ parentId: number | null,
+ node: MenuItem,
+): MenuItem[] {
+ if (parentId === null) return [...items, node];
+ return items.map((item) => {
+ if (item.id === parentId) {
+ return { ...item, children: [...(item.children || []), node] };
+ }
+ if (!item.children?.length) return item;
+ return { ...item, children: addNode(item.children, parentId, node) };
+ });
+}
+
+function toParentOptions(items: MenuItem[]): Array<{
+ title: string;
+ value: number;
+ children?: Array<{ title: string; value: number }>;
+}> {
+ return items.map((item) => ({
+ title: item.title,
+ value: item.id,
+ children: item.children?.map((child) => ({
+ title: child.title,
+ value: child.id,
+ })),
+ }));
+}
+
+export default function SysMenuPage() {
+ const [menus, setMenus] = useState(() => []);
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const [modalOpen, setModalOpen] = useState(false);
+ const [modalType, setModalType] = useState<'create' | 'update'>('create');
+ const [currentItem, setCurrentItem] = useState(
+ undefined,
+ );
+ const [sortOpen, setSortOpen] = useState(false);
+
+ const parentOptions = useMemo(() => toParentOptions(menus), [menus]);
+
+ const handleCreate = () => {
+ setModalType('create');
+ setCurrentItem(undefined);
+ setModalOpen(true);
+ };
+
+ const handleEdit = (item: MenuItem) => {
+ setModalType('update');
+ setCurrentItem(item);
+ setModalOpen(true);
+ };
+
+ const handleDelete = async (id: number) => {
+ try {
+ await menuService.deleteMenu(id);
+ setMenus((prev) => normalizeChildren(removeNode(prev, id)));
+ setSelectedRowKeys((prev) => prev.filter((k) => Number(k) !== id));
+ message.success('删除成功');
+ } catch (e) {
+ console.error(e);
+ }
+ };
+
+ const handleSave = async (values: MenuFormValues) => {
+ try {
+ if (modalType === 'create') {
+ const resp = await menuService.createMenu(values);
+ const added = resp as MenuItem;
+ setMenus((prev) =>
+ normalizeChildren(addNode(prev, values.parentId, added)),
+ );
+ message.success('新增成功');
+ } else if (currentItem) {
+ await menuService.updateMenu(
+ currentItem.id,
+ values as Partial,
+ );
+ setMenus((prev) =>
+ normalizeChildren(
+ updateNode(prev, currentItem.id, (node) => ({
+ ...node,
+ ...values,
+ })),
+ ),
+ );
+ message.success('修改成功');
+ }
+ setModalOpen(false);
+ } catch (e) {
+ console.error(e);
+ }
+ };
+
+ const handleToggleSelectedStatus = () => {
+ if (!selectedRowKeys.length) return;
+ const selectedSet = new Set(selectedRowKeys.map((k) => Number(k)));
+ setMenus((prev) =>
+ normalizeChildren(
+ mapTree(prev, (item) => {
+ if (!selectedSet.has(item.id)) return item;
+ return { ...item, status: item.status === 1 ? 0 : 1 };
+ }),
+ ),
+ );
+ message.success('选中项状态已切换');
+ };
+
+ const handleSortConfirm = async (updatedMenus: MenuItem[]) => {
+ try {
+ // attempt to persist ordering to API; API may accept different shapes
+ try {
+ await menuService.sortMenus(updatedMenus as any);
+ } catch (e) {
+ // ignore API errors but still update local state
+ }
+
+ // ensure sort fields are normalized and flatten structure to set state
+ const normalize = (
+ items: MenuItem[],
+ parentId: number | null = null,
+ ): MenuItem[] => {
+ return items.map((it: MenuItem, idx: number) => ({
+ ...it,
+ parentId,
+ sort: idx + 1,
+ children: it.children ? normalize(it.children, it.id) : undefined,
+ }));
+ };
+
+ const normalized: MenuItem[] = normalize(updatedMenus);
+ setMenus(
+ normalized.map((it: MenuItem) => ({
+ ...it,
+ children: it.children && it.children.length ? it.children : undefined,
+ })),
+ );
+ setSortOpen(false);
+ message.success('排序已更新');
+ } catch (e) {
+ console.error(e);
+ }
+ };
+
+ // load menus from API on mount
+ const isMounted = useIsMounted();
+ React.useEffect(() => {
+ (async () => {
+ try {
+ const list = await menuService.listMenus();
+ if (isMounted()) setMenus(normalizeChildren(list as MenuItem[]));
+ } catch (e) {
+ // keep initial menus on error
+ }
+ })();
+ }, [isMounted]);
+
+ return (
+
+ setSortOpen(true)}
+ onToggleSelectedStatus={handleToggleSelectedStatus}
+ />
+
+ a.sort - b.sort)}
+ selectedRowKeys={selectedRowKeys}
+ onSelectChange={setSelectedRowKeys}
+ onEditItem={handleEdit}
+ onDeleteItem={handleDelete}
+ />
+
+ setModalOpen(false)}
+ />
+
+ setSortOpen(false)}
+ onConfirm={handleSortConfirm}
+ />
+
+ );
+}
diff --git a/src/pages/user/[id]/index.js b/src/pages/user/[id]/index.js
deleted file mode 100644
index 5c7614c..0000000
--- a/src/pages/user/[id]/index.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { connect } from 'umi'
-import { Page } from 'components'
-import styles from './index.less'
-
-@connect(({ userDetail }) => ({ userDetail }))
-class UserDetail extends PureComponent {
- render() {
- const { userDetail } = this.props
- const { data } = userDetail
- const content = []
- for (let key in data) {
- if ({}.hasOwnProperty.call(data, key)) {
- content.push(
-
-
{key}
-
{String(data[key])}
-
- )
- }
- }
- return (
-
- {content}
-
- )
- }
-}
-
-UserDetail.propTypes = {
- userDetail: PropTypes.object,
-}
-
-export default UserDetail
diff --git a/src/pages/user/[id]/index.less b/src/pages/user/[id]/index.less
deleted file mode 100644
index fa9c7d4..0000000
--- a/src/pages/user/[id]/index.less
+++ /dev/null
@@ -1,14 +0,0 @@
-.content {
- line-height: 2.4;
- font-size: 13px;
-
- .item {
- display: flex;
-
- & > div {
- &:first-child {
- width: 100px;
- }
- }
- }
-}
diff --git a/src/pages/user/[id]/models/detail.js b/src/pages/user/[id]/models/detail.js
deleted file mode 100644
index b2fe6b0..0000000
--- a/src/pages/user/[id]/models/detail.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { pathToRegexp } from 'path-to-regexp'
-import api from 'api'
-
-const { queryUser } = api
-
-const userDetailModel = {
- namespace: 'userDetail',
-
- state: {
- data: {},
- },
-
- subscriptions: {
- setup({ dispatch, history }) {
- history.listen(({ pathname }) => {
- const match = pathToRegexp('/user/:id').exec(pathname)
- if (match) {
- dispatch({ type: 'query', payload: { id: match[1] } })
- }
- })
- },
- },
-
- effects: {
- *query({ payload }, { call, put }) {
- const data = yield call(queryUser, payload)
- const { success, message, status, ...other } = data
- if (success) {
- yield put({
- type: 'querySuccess',
- payload: {
- data: other,
- },
- })
- } else {
- throw data
- }
- },
- },
-
- reducers: {
- querySuccess(state, { payload }) {
- const { data } = payload
- return {
- ...state,
- data,
- }
- },
- },
-}
-
-export default userDetailModel
diff --git a/src/pages/user/components/Filter.js b/src/pages/user/components/Filter.js
deleted file mode 100644
index c1e90b7..0000000
--- a/src/pages/user/components/Filter.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import dayjs from 'dayjs'
-import { FilterItem } from 'components'
-import { i18n } from '@lingui/core'
-import { Button, Row, Col, DatePicker, Form, Input, Cascader } from 'antd'
-import city from 'utils/city'
-
-const { Search } = Input
-const { RangePicker } = DatePicker
-
-const ColProps = {
- xs: 24,
- sm: 12,
- style: {
- marginBottom: 16,
- },
-}
-
-const TwoColProps = {
- ...ColProps,
- xl: 96,
-}
-
-class Filter extends Component {
- formRef = React.createRef()
-
- handleFields = fields => {
- const { createTime } = fields
- if (createTime && createTime.length) {
- fields.createTime = [
- dayjs(createTime[0]).format('YYYY-MM-DD'),
- dayjs(createTime[1]).format('YYYY-MM-DD'),
- ]
- }
- return fields
- }
-
- handleSubmit = () => {
- const { onFilterChange } = this.props
- const values = this.formRef.current.getFieldsValue()
- const fields = this.handleFields(values)
- onFilterChange(fields)
- }
-
- handleReset = () => {
- const fields = this.formRef.current.getFieldsValue()
- for (let item in fields) {
- if ({}.hasOwnProperty.call(fields, item)) {
- if (fields[item] instanceof Array) {
- fields[item] = []
- } else {
- fields[item] = undefined
- }
- }
- }
- this.formRef.current.setFieldsValue(fields)
- this.handleSubmit()
- }
- handleChange = (key, values) => {
- const { onFilterChange } = this.props
- let fields = this.formRef.current.getFieldsValue()
- fields[key] = values
- fields = this.handleFields(fields)
- onFilterChange(fields)
- }
-
- render() {
- const { onAdd, filter } = this.props
- const { name, address } = filter
-
- let initialCreateTime = []
- if (filter.createTime && filter.createTime[0]) {
- initialCreateTime[0] = dayjs(filter.createTime[0])
- }
- if (filter.createTime && filter.createTime[1]) {
- initialCreateTime[1] = dayjs(filter.createTime[1])
- }
-
- return (
-
- )
- }
-}
-
-Filter.propTypes = {
- onAdd: PropTypes.func,
- filter: PropTypes.object,
- onFilterChange: PropTypes.func,
-}
-
-export default Filter
diff --git a/src/pages/user/components/List.js b/src/pages/user/components/List.js
deleted file mode 100644
index 938c7d8..0000000
--- a/src/pages/user/components/List.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { Table, Modal, Avatar } from 'antd'
-import { DropOption } from 'components'
-import { i18n } from '@lingui/core'
-import { Link } from 'umi'
-import styles from './List.less'
-
-const { confirm } = Modal
-
-class List extends PureComponent {
- handleMenuClick = (record, e) => {
- const { onDeleteItem, onEditItem } = this.props
-
- if (e.key === '1') {
- onEditItem(record)
- } else if (e.key === '2') {
- confirm({
- title: i18n._('Are you sure delete this record?'),
- onOk() {
- onDeleteItem(record.id)
- },
- })
- }
- }
-
- render() {
- const { onDeleteItem, onEditItem, ...tableProps } = this.props
-
- const columns = [
- {
- title: i18n._('Avatar'),
- dataIndex: 'avatar',
- key: 'avatar',
- width: '7%',
- fixed: 'left',
- render: text => ,
- },
- {
- title: i18n._('Name'),
- dataIndex: 'name',
- key: 'name',
- render: (text, record) => {text},
- },
- {
- title: i18n._('NickName'),
- dataIndex: 'nickName',
- key: 'nickName',
- },
- {
- title: i18n._('Age'),
- dataIndex: 'age',
- width: '6%',
- key: 'age',
- },
- {
- title: i18n._('Gender'),
- dataIndex: 'isMale',
- key: 'isMale',
- width: '7%',
- render: text => {text ? i18n._('Male') : i18n._('Female')} ,
- },
- {
- title: i18n._('Phone'),
- dataIndex: 'phone',
- key: 'phone',
- },
- {
- title: i18n._('Email'),
- dataIndex: 'email',
- key: 'email',
- },
- {
- title: i18n._('Address'),
- dataIndex: 'address',
- key: 'address',
- },
- {
- title: i18n._('CreateTime'),
- dataIndex: 'createTime',
- key: 'createTime',
- },
- {
- title: i18n._('Operation'),
- key: 'operation',
- fixed: 'right',
- width: '8%',
- render: (text, record) => {
- return (
- this.handleMenuClick(record, e)}
- menuOptions={[
- { key: '1', name: i18n._('Update') },
- { key: '2', name: i18n._('Delete') },
- ]}
- />
- )
- },
- },
- ]
-
- return (
- i18n._('Total {total} Items', { total }),
- }}
- className={styles.table}
- bordered
- scroll={{ x: 1200 }}
- columns={columns}
- simple
- rowKey={record => record.id}
- />
- )
- }
-}
-
-List.propTypes = {
- onDeleteItem: PropTypes.func,
- onEditItem: PropTypes.func,
- location: PropTypes.object,
-}
-
-export default List
diff --git a/src/pages/user/components/List.less b/src/pages/user/components/List.less
deleted file mode 100644
index 6d35765..0000000
--- a/src/pages/user/components/List.less
+++ /dev/null
@@ -1,7 +0,0 @@
-.table {
- :global {
- .ant-table td {
- white-space: nowrap;
- }
- }
-}
diff --git a/src/pages/user/components/Modal.js b/src/pages/user/components/Modal.js
deleted file mode 100644
index 78e084a..0000000
--- a/src/pages/user/components/Modal.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd'
-import { i18n } from '@lingui/core'
-import city from 'utils/city'
-
-const FormItem = Form.Item
-
-const formItemLayout = {
- labelCol: {
- span: 6,
- },
- wrapperCol: {
- span: 14,
- },
-}
-
-class UserModal extends PureComponent {
- formRef = React.createRef()
-
- handleOk = () => {
- const { item = {}, onOk } = this.props
-
- this.formRef.current.validateFields()
- .then(values => {
- const data = {
- ...values,
- key: item.key,
- }
- data.address = data.address.join(' ')
- onOk(data)
- })
- .catch(errorInfo => {
- if (process.env.NODE_ENV !== 'production') {
- // eslint-disable-next-line no-console
- console.error(errorInfo)
- }
- })
- }
-
- render() {
- const { item = {}, onOk, form, ...modalProps } = this.props
-
- return (
-
-
-
- );
- }
-}
-
-UserModal.propTypes = {
- type: PropTypes.string,
- item: PropTypes.object,
- onOk: PropTypes.func,
-}
-
-export default UserModal
diff --git a/src/pages/user/index.js b/src/pages/user/index.js
deleted file mode 100644
index aea806f..0000000
--- a/src/pages/user/index.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import React, { PureComponent } from 'react'
-import PropTypes from 'prop-types'
-import { history } from 'umi'
-import { connect } from 'umi'
-import { Row, Col, Button, Popconfirm } from 'antd'
-import { i18n } from '@lingui/core'
-import { Page } from 'components'
-import { stringify } from 'qs'
-import List from './components/List'
-import Filter from './components/Filter'
-import Modal from './components/Modal'
-
-@connect(({ user, loading }) => ({ user, loading }))
-class User extends PureComponent {
- handleRefresh = newQuery => {
- const { location } = this.props
- const { query, pathname } = location
-
- history.push({
- pathname,
- search: stringify(
- {
- ...query,
- ...newQuery,
- },
- { arrayFormat: 'repeat' }
- ),
- })
- }
-
- handleDeleteItems = () => {
- const { dispatch, user } = this.props
- const { list, pagination, selectedRowKeys } = user
-
- dispatch({
- type: 'user/multiDelete',
- payload: {
- ids: selectedRowKeys,
- },
- }).then(() => {
- this.handleRefresh({
- page:
- list.length === selectedRowKeys.length && pagination.current > 1
- ? pagination.current - 1
- : pagination.current,
- })
- })
- }
-
- get modalProps() {
- const { dispatch, user, loading } = this.props
- const { currentItem, modalOpen, modalType } = user
-
- return {
- item: modalType === 'create' ? {} : currentItem,
- open: modalOpen,
- destroyOnHidden: true,
- mask: { closable: false },
- confirmLoading: loading.effects[`user/${modalType}`],
- title: `${
- modalType === 'create' ? i18n._('Create User') : i18n._('Update User')
- }`,
- centered: true,
- onOk: data => {
- dispatch({
- type: `user/${modalType}`,
- payload: data,
- }).then(() => {
- this.handleRefresh()
- })
- },
- onCancel() {
- dispatch({
- type: 'user/hideModal',
- })
- },
- }
- }
-
- get listProps() {
- const { dispatch, user, loading } = this.props
- const { list, pagination, selectedRowKeys } = user
-
- return {
- dataSource: list,
- loading: loading.effects['user/query'],
- pagination,
- onChange: page => {
- this.handleRefresh({
- page: page.current,
- pageSize: page.pageSize,
- })
- },
- onDeleteItem: id => {
- dispatch({
- type: 'user/delete',
- payload: id,
- }).then(() => {
- this.handleRefresh({
- page:
- list.length === 1 && pagination.current > 1
- ? pagination.current - 1
- : pagination.current,
- })
- })
- },
- onEditItem(item) {
- dispatch({
- type: 'user/showModal',
- payload: {
- modalType: 'update',
- currentItem: item,
- },
- })
- },
- rowSelection: {
- selectedRowKeys,
- onChange: keys => {
- dispatch({
- type: 'user/updateState',
- payload: {
- selectedRowKeys: keys,
- },
- })
- },
- },
- }
- }
-
- get filterProps() {
- const { location, dispatch } = this.props
- const { query } = location
-
- return {
- filter: {
- ...query,
- },
- onFilterChange: value => {
- this.handleRefresh({
- ...value,
- })
- },
- onAdd() {
- dispatch({
- type: 'user/showModal',
- payload: {
- modalType: 'create',
- },
- })
- },
- }
- }
-
- render() {
- const { user } = this.props
- const { selectedRowKeys } = user
-
- return (
-
-
- {selectedRowKeys.length > 0 && (
-
-
- {`Selected ${selectedRowKeys.length} items `}
-
-
- Remove
-
-
-
-
- )}
-
-
-
- )
- }
-}
-
-User.propTypes = {
- user: PropTypes.object,
- location: PropTypes.object,
- dispatch: PropTypes.func,
- loading: PropTypes.object,
-}
-
-export default User
diff --git a/src/pages/user/model.js b/src/pages/user/model.js
deleted file mode 100644
index a05f765..0000000
--- a/src/pages/user/model.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import modelExtend from 'dva-model-extend'
-import { pathToRegexp } from 'path-to-regexp'
-import api from 'api'
-import { pageModel } from 'utils/model'
-
-const {
- queryUserList,
- createUser,
- removeUser,
- updateUser,
- removeUserList,
-} = api
-
-export default modelExtend(pageModel, {
- namespace: 'user',
-
- state: {
- currentItem: {},
- modalOpen: false,
- modalType: 'create',
- selectedRowKeys: [],
- },
-
- subscriptions: {
- setup({ dispatch, history }) {
- history.listen(location => {
- if (pathToRegexp('/user').exec(location.pathname)) {
- const payload = location.query || { page: 1, pageSize: 10 }
- dispatch({
- type: 'query',
- payload,
- })
- }
- })
- },
- },
-
- effects: {
- *query({ payload = {} }, { call, put }) {
- const data = yield call(queryUserList, payload)
- if (data) {
- yield put({
- type: 'querySuccess',
- payload: {
- list: data.data,
- pagination: {
- current: Number(payload.page) || 1,
- pageSize: Number(payload.pageSize) || 10,
- total: data.total,
- },
- },
- })
- }
- },
-
- *delete({ payload }, { call, put, select }) {
- const data = yield call(removeUser, { id: payload })
- const { selectedRowKeys } = yield select(_ => _.user)
- if (data.success) {
- yield put({
- type: 'updateState',
- payload: {
- selectedRowKeys: selectedRowKeys.filter(_ => _ !== payload),
- },
- })
- } else {
- throw data
- }
- },
-
- *multiDelete({ payload }, { call, put }) {
- const data = yield call(removeUserList, payload)
- if (data.success) {
- yield put({ type: 'updateState', payload: { selectedRowKeys: [] } })
- } else {
- throw data
- }
- },
-
- *create({ payload }, { call, put }) {
- const data = yield call(createUser, payload)
- if (data.success) {
- yield put({ type: 'hideModal' })
- } else {
- throw data
- }
- },
-
- *update({ payload }, { select, call, put }) {
- const id = yield select(({ user }) => user.currentItem.id)
- const newUser = { ...payload, id }
- const data = yield call(updateUser, newUser)
- if (data.success) {
- yield put({ type: 'hideModal' })
- } else {
- throw data
- }
- },
- },
-
- reducers: {
- showModal(state, { payload }) {
- return { ...state, ...payload, modalOpen: true }
- },
-
- hideModal(state) {
- return { ...state, modalOpen: false }
- },
- },
-})
diff --git a/src/pages/users/$id.tsx b/src/pages/users/$id.tsx
new file mode 100644
index 0000000..9c86bc6
--- /dev/null
+++ b/src/pages/users/$id.tsx
@@ -0,0 +1,163 @@
+import useIsMounted from '@/hooks/useIsMounted';
+import { getUser } from '@/services/user';
+import type { User } from '@/types/user';
+import {
+ Avatar,
+ Button,
+ Card,
+ Col,
+ Descriptions,
+ Row,
+ Spin,
+ Statistic,
+ Tag,
+ message,
+} from 'antd';
+
+import { startTransition, useEffect, useState } from 'react';
+import { useParams } from 'umi';
+
+export default function UserDetail() {
+ const params = useParams<{ id: string }>();
+ const { id } = params;
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ try {
+ const resp = await getUser(String(id));
+ if (!isMounted()) return;
+ startTransition(() => {
+ setUser(resp || null);
+ });
+ } catch (e) {
+ // log and continue
+ console.error(e);
+ } finally {
+ if (isMounted()) setLoading(false);
+ }
+ })();
+ }, [id, isMounted]);
+
+ if (loading)
+ return (
+
+
+
+ );
+ if (!user) return 未找到用户 ;
+
+ const gender =
+ typeof user.isMale === 'boolean' ? (user.isMale ? '男' : '女') : '未知';
+
+ return (
+
+
+
+
+
+
+ {user.name || user.nickName || user.id}{' '}
+
+
+ {user.bio}
+
+
+ message.info('编辑暂未实现')}
+ >
+ 编辑
+
+ {
+ navigator.clipboard?.writeText(String(user.id));
+ message.success('已复制用户 ID');
+ }}
+ >
+ 复制 ID
+
+
+
+
+
+
+
+
+ {user.status === 'active' ? '活跃' : '未激活'}
+
+
+
+
+
+
+
+ {user.id}
+ {user.name}
+ {user.nickName}
+ {user.phone}
+ {user.age}
+ {gender}
+
+ {(user as any).phoneModel || '-'}
+
+ {user.email}
+
+ {user.address}
+
+
+ {user.createTime}
+
+
+
+
+
+ {(
+ ((user as any).browsingHistory || []) as Array<{
+ url: string;
+ title: string;
+ time: string;
+ }>
+ ).map((it) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/users/list.tsx b/src/pages/users/list.tsx
new file mode 100644
index 0000000..90ee111
--- /dev/null
+++ b/src/pages/users/list.tsx
@@ -0,0 +1,126 @@
+import useIsMounted from '@/hooks/useIsMounted';
+import { listUsers } from '@/services/user';
+import type { PagedResponse, User } from '@/types/user';
+import { Card, Input, Table } from 'antd';
+
+import {
+ startTransition,
+ useCallback,
+ useDeferredValue,
+ useEffect,
+ useState,
+} from 'react';
+
+const { Search } = Input;
+
+export default function UserList() {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [page, setPage] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [total, setTotal] = useState(0);
+ const [keyword, setKeyword] = useState('');
+
+ const fetch = useCallback(
+ async (p = page, ps = pageSize, k = keyword) => {
+ setLoading(true);
+ try {
+ const resp = (await listUsers({
+ page: p,
+ pageSize: ps,
+ name: k,
+ })) as PagedResponse;
+ startTransition(() => {
+ setData(resp?.data || []);
+ setTotal(resp?.total ?? (resp?.data ? resp.data.length : 0));
+ });
+ } catch (e) {
+ startTransition(() => {
+ setData([]);
+ setTotal(0);
+ });
+ } finally {
+ setLoading(false);
+ }
+ },
+ [page, pageSize, keyword],
+ );
+
+ // defer large list rendering to avoid jank on immediate updates
+ const deferredData = useDeferredValue(data);
+ const deferredTotal = useDeferredValue(total);
+
+ const isMounted = useIsMounted();
+ useEffect(() => {
+ (async () => {
+ if (!isMounted()) return;
+ await fetch(page, pageSize, keyword);
+ })();
+ }, [fetch, page, pageSize, keyword, isMounted]);
+
+ const onDetail = (item: User) => {
+ window.open(`/users/${item.id}`);
+ };
+
+ const handleTableChange = (p: { current?: number; pageSize?: number }) => {
+ const nextPage = p.current || 1;
+ const nextSize = p.pageSize || 10;
+ startTransition(() => {
+ setPage(nextPage);
+ setPageSize(nextSize);
+ });
+ fetch(nextPage, nextSize, keyword);
+ };
+
+ const columns = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ render: (_: unknown, record: User) => (
+ onDetail(record)}>{record.name}
+ ),
+ },
+ { title: 'Nick', dataIndex: 'nickName' },
+ { title: 'Phone', dataIndex: 'phone' },
+ { title: 'Age', dataIndex: 'age' },
+ { title: 'Email', dataIndex: 'email' },
+ { title: 'Address', dataIndex: 'address' },
+ { title: 'Created', dataIndex: 'createTime' },
+ ];
+
+ return (
+
+
+ {
+ setKeyword(v);
+ setPage(1);
+ }}
+ enterButton
+ allowClear
+ style={{ width: 260 }}
+ />
+
+ r.id}
+ loading={loading}
+ pagination={{
+ current: page,
+ pageSize,
+ total: deferredTotal,
+ showSizeChanger: true,
+ }}
+ onChange={handleTableChange}
+ />
+
+ );
+}
diff --git a/src/plugins/onError.js b/src/plugins/onError.js
deleted file mode 100644
index de427a5..0000000
--- a/src/plugins/onError.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { message } from 'antd'
-
-const onErrorHandler = {
- onError(e, a) {
- e.preventDefault()
- if (e.message) {
- message.error(e.message)
- } else {
- // eslint-disable-next-line no-console
- console.error(e)
- }
- },
-}
-
-export default onErrorHandler
diff --git a/src/services/admin.ts b/src/services/admin.ts
new file mode 100644
index 0000000..47a0d4e
--- /dev/null
+++ b/src/services/admin.ts
@@ -0,0 +1,76 @@
+/**
+ * Admin 服务:封装关于管理员(admin)相关的后端 API 调用。
+ *
+ * 对 LLM 的说明:
+ * - 导出函数均为对后端 REST 接口的轻量封装,返回值由 `src/utils/request.ts` 统一处理(JSON 解析与错误抛出)。
+ * - 常用函数:`login`, `logout`, `currentAdmin`, `listAdmins`, `createAdmin`, `getAdmin`, `updateAdmin`, `deleteAdmin`, `deleteAdmins`。
+ * - 参数与返回类型:尽量参考 `src/types/admin.ts` 中的 `Admin` / `AdminResponse` 定义以了解字段结构。
+ *
+ * 使用示例:
+ * ```ts
+ * const admin = await login({ adminname: 'root', password: 'pwd' });
+ * const list = await listAdmins({ page: 1 });
+ * ```
+ */
+import api from '../utils/request';
+
+export async function login(values: { adminname: string; password: string }) {
+ const payload = {
+ username: values.adminname,
+ password: values.password,
+ };
+ return api('/admin/login', { method: 'POST', body: payload });
+}
+
+export async function logout() {
+ return api('/admin/logout');
+}
+
+export async function currentAdmin(): Promise<
+ import('@/types/admin').Admin | undefined
+> {
+ return api('/admin');
+}
+
+export async function listAdmins(
+ params?: Record,
+): Promise<{ data: import('@/types/admin').Admin[]; total?: number }> {
+ return api<{ data: import('@/types/admin').Admin[]; total?: number }>(
+ '/admins',
+ { method: 'GET', params },
+ );
+}
+
+export async function createAdmin(values: Record) {
+ return api('/admin', { method: 'POST', body: values });
+}
+
+export async function getAdmin(
+ id: string,
+): Promise {
+ return api(`/admin/${id}`);
+}
+
+export async function updateAdmin(id: string, values: Record) {
+ return api(`/admin/${id}`, { method: 'PATCH', body: values });
+}
+
+export async function deleteAdmin(id: string) {
+ return api(`/admin/${id}`, { method: 'DELETE' });
+}
+
+export async function deleteAdmins(ids: string[]) {
+ return api('/admins/delete', { method: 'POST', body: { ids } });
+}
+
+export default {
+ login,
+ logout,
+ currentAdmin,
+ listAdmins,
+ createAdmin,
+ getAdmin,
+ updateAdmin,
+ deleteAdmin,
+ deleteAdmins,
+};
diff --git a/src/services/api.js b/src/services/api.js
deleted file mode 100644
index dd5caf8..0000000
--- a/src/services/api.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const api = {
- queryRouteList: '/routes',
-
- queryUserInfo: '/user',
- logoutUser: '/user/logout',
- loginUser: 'POST /user/login',
-
- queryUser: '/user/:id',
- queryUserList: '/users',
- updateUser: 'Patch /user/:id',
- createUser: 'POST /user',
- removeUser: 'DELETE /user/:id',
- removeUserList: 'POST /users/delete',
-
- queryPostList: '/posts',
-
- queryDashboard: '/dashboard',
-}
-
-export default api
diff --git a/src/services/dashboard.ts b/src/services/dashboard.ts
new file mode 100644
index 0000000..30e9a5f
--- /dev/null
+++ b/src/services/dashboard.ts
@@ -0,0 +1,13 @@
+/**
+ * Dashboard 服务:获取仪表盘汇总数据的 API 封装。
+ *
+ * 对 LLM 的说明:
+ * - `getDashboard()` 直接调用 `/dashboard`,返回后端提供的汇总 JSON 数据。
+ */
+import api from '../utils/request';
+
+export async function getDashboard() {
+ return api('/dashboard');
+}
+
+export default { getDashboard };
diff --git a/src/services/index.js b/src/services/index.js
deleted file mode 100644
index 67236ea..0000000
--- a/src/services/index.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import request from 'utils/request'
-import { apiPrefix } from 'utils/config'
-
-import api from './api'
-
-const gen = params => {
- let url = apiPrefix + params
- let method = 'GET'
-
- const paramsArray = params.split(' ')
- if (paramsArray.length === 2) {
- method = paramsArray[0]
- url = apiPrefix + paramsArray[1]
- }
-
- return function(data) {
- return request({
- url,
- data,
- method,
- })
- }
-}
-
-const APIFunction = {}
-for (const key in api) {
- APIFunction[key] = gen(api[key])
-}
-
-APIFunction.queryWeather = params => {
- params.key = 'i7sau1babuzwhycn'
- return request({
- url: `${apiPrefix}/weather/now.json`,
- data: params,
- })
-}
-
-export default APIFunction
diff --git a/src/services/menu.ts b/src/services/menu.ts
new file mode 100644
index 0000000..21dd53e
--- /dev/null
+++ b/src/services/menu.ts
@@ -0,0 +1,34 @@
+/**
+ * Menu 服务:菜单相关的后端 API 封装。
+ *
+ * 对 LLM 的说明:
+ * - 提供列表、创建、更新、删除、排序等基本操作的封装函数。
+ * - 返回值与参数类型参见 `src/types/menu.ts` 中的 `MenuItem` 与 `MenuFormValues`。
+ */
+import type { MenuFormValues, MenuItem } from '../types/menu';
+import api from '../utils/request';
+
+export async function listMenus(): Promise {
+ return api('/menus');
+}
+
+export async function createMenu(values: MenuFormValues) {
+ return api('/menu', { method: 'POST', body: values });
+}
+
+export async function updateMenu(
+ id: number | string,
+ values: Partial,
+) {
+ return api(`/menu/${id}`, { method: 'PATCH', body: values });
+}
+
+export async function deleteMenu(id: number | string) {
+ return api('/menu/delete', { method: 'POST', body: { id } });
+}
+
+export async function sortMenus(order: Array) {
+ return api('/menu/sort', { method: 'POST', body: { order } });
+}
+
+export default { listMenus, createMenu, updateMenu, deleteMenu, sortMenus };
diff --git a/src/services/user.ts b/src/services/user.ts
new file mode 100644
index 0000000..c62c97c
--- /dev/null
+++ b/src/services/user.ts
@@ -0,0 +1,49 @@
+/**
+ * User 服务:用户相关的后端 API 封装。
+ *
+ * 对 LLM 的说明:
+ * - 提供分页/列表查询、创建、查询单个、更新、删除、批量删除等操作。
+ * - 返回类型建议参考 `src/types/user.ts` 中的 `User` 和 `PagedResponse`。
+ */
+import type { PagedResponse, User } from '@/types/user';
+import api from '../utils/request';
+
+export async function listUsers(
+ params?: Record,
+): Promise> {
+ return api>('/users', { method: 'GET', params });
+}
+
+export async function createUser(
+ values: Record,
+): Promise {
+ return api('/user', { method: 'POST', body: values });
+}
+
+export async function getUser(id: string): Promise {
+ return api(`/user/${id}`);
+}
+
+export async function updateUser(
+ id: string,
+ values: Record,
+): Promise {
+ return api(`/user/${id}`, { method: 'PATCH', body: values });
+}
+
+export async function deleteUser(id: string) {
+ return api(`/user/${id}`, { method: 'DELETE' });
+}
+
+export async function deleteUsers(ids: string[]) {
+ return api('/users/delete', { method: 'POST', body: { ids } });
+}
+
+export default {
+ listUsers,
+ createUser,
+ getUser,
+ updateUser,
+ deleteUser,
+ deleteUsers,
+};
diff --git a/src/setupTests.ts b/src/setupTests.ts
new file mode 100644
index 0000000..7b0828b
--- /dev/null
+++ b/src/setupTests.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';
diff --git a/src/store/useGlobalStore.ts b/src/store/useGlobalStore.ts
new file mode 100644
index 0000000..1d0c8a5
--- /dev/null
+++ b/src/store/useGlobalStore.ts
@@ -0,0 +1,78 @@
+/**
+ * 全局状态(Zustand)store
+ *
+ * 对 LLM 的说明:
+ * - 存储与 AI 配置相关的持久化配置(`aiConfig`、`activeProvider`)以及简单的认证状态(`isLoggedIn`、`currentAdmin`)。
+ * - 提供对 AI 配置的读取/保存/重置接口,内部通过 `../pages/ai/aiConfig` 的方法与 `localStorage` 协同工作。
+ * - 认证方法:`login(token?)` 会在 localStorage 中写入 `app-auth-token`,`logout()` 会清除该值并重置用户状态。
+ *
+ * 对自动化/LLM:如果需要模拟登录,请在测试环境中写入 `app-auth-token`,并调用 `setCurrentUser` 以设置 `currentAdmin`。
+ */
+import { create } from 'zustand';
+
+import { Admin } from '@/types/admin';
+import {
+ DEFAULT_CONFIG,
+ loadAIConfig,
+ loadActiveProvider,
+ saveAIConfig,
+ saveActiveProvider,
+ type ConfigState,
+ type ProviderKey,
+} from '../pages/ai/aiConfig';
+
+export type GlobalState = {
+ aiConfig: ConfigState;
+ activeProvider: ProviderKey;
+ setAiConfig: (config: ConfigState) => void;
+ setActiveProvider: (provider: ProviderKey) => void;
+ resetProvider: (provider: ProviderKey) => void;
+ // auth
+ isLoggedIn: boolean;
+ currentAdmin?: Admin;
+ setCurrentUser: (u?: Admin | undefined) => void;
+ login: (token?: string) => void;
+ logout: () => void;
+};
+
+const useGlobalStore = create((set, get) => ({
+ aiConfig: loadAIConfig(),
+ activeProvider: loadActiveProvider(),
+ isLoggedIn: Boolean(localStorage.getItem('app-auth-token')),
+ currentAdmin: undefined,
+
+ setAiConfig(config: ConfigState) {
+ set({ aiConfig: config });
+ saveAIConfig(config);
+ },
+
+ setActiveProvider(provider: ProviderKey) {
+ set({ activeProvider: provider });
+ saveActiveProvider(provider);
+ },
+
+ resetProvider(provider: ProviderKey) {
+ const next = {
+ ...get().aiConfig,
+ [provider]: DEFAULT_CONFIG[provider],
+ } as ConfigState;
+ set({ aiConfig: next });
+ saveAIConfig(next);
+ },
+ // auth methods
+ login(token?: string) {
+ const t = token || 'token-' + Date.now();
+
+ localStorage.setItem('app-auth-token', t);
+ set({ isLoggedIn: true });
+ },
+ setCurrentUser(u?: Admin | undefined) {
+ set({ currentAdmin: u });
+ },
+ logout() {
+ localStorage.removeItem('app-auth-token');
+ set({ isLoggedIn: false, currentAdmin: undefined });
+ },
+}));
+
+export default useGlobalStore;
diff --git a/src/themes/default.less b/src/themes/default.less
deleted file mode 100644
index d2ab562..0000000
--- a/src/themes/default.less
+++ /dev/null
@@ -1,25 +0,0 @@
-// 本文件是对 ant-design:
-// https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
-// 相应变量值的覆盖
-// 注意:只需写出要覆盖的变量即可(不需要覆盖的变量不要写)
-
-/* @import '../../node_modules/antd/lib/style/themes/default.less';*/
-
-@border-radius-base: 3px;
-@border-radius-sm: 2px;
-@shadow-color: rgba(0, 0, 0, 0.05);
-@shadow-1-down: 4px 4px 40px @shadow-color;
-@border-color-split: #f4f4f4;
-@border-color-base: #e5e5e5;
-@font-size-base: 13px;
-@text-color: #666;
-@hover-color: #f9f9fc;
-
-// Legacy token fallbacks (required by project's Less files)
-@primary-color: #1890ff;
-@success-color: #52c41a;
-@warning-color: #faad14;
-@error-color: #ff4d4f;
-@outline-fade: 20%;
-@text-color-secondary: fade(@text-color, 35%);
-@ease-in-out: ease-in-out;
diff --git a/src/themes/index.less b/src/themes/index.less
deleted file mode 100644
index 6afc6e9..0000000
--- a/src/themes/index.less
+++ /dev/null
@@ -1,125 +0,0 @@
-@import '~antd/dist/reset.css';
-@import '~themes/vars.less';
-
-body {
- height: 100%;
- overflow-y: auto;
- background-color: #f8f8f8;
-}
-
-::-webkit-scrollbar-thumb {
- background-color: #e6e6e6;
-}
-
-::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-.margin-right {
- margin-right: 16px;
-}
-
-:global {
- .ant-breadcrumb {
- & > span {
- &:last-child {
- color: #999;
- font-weight: normal;
- }
- }
- }
-
- .ant-breadcrumb-link {
- .anticon + span {
- margin-left: 4px;
- }
- }
-
- .ant-table {
- .ant-table-thead > tr > th {
- text-align: center;
- }
-
- .ant-table-tbody > tr > td {
- text-align: center;
- }
-
- &.ant-table-small {
- .ant-table-thead > tr > th {
- background: #f7f7f7;
- }
-
- .ant-table-body > table {
- padding: 0;
- }
- }
- }
-
- .ant-table-pagination {
- float: none !important;
- display: table;
- margin: 16px auto !important;
- }
-
- .ant-popover-inner {
- border: none;
- border-radius: 0;
- box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
- }
-
- .ant-form-item-control {
- vertical-align: middle;
- }
-
- .ant-modal-mask {
- background-color: rgba(55, 55, 55, 0.2);
- }
-
- .ant-modal-content {
- box-shadow: none;
- }
-
- .ant-select-dropdown-menu-item {
- padding: 12px 16px !important;
- }
-
- a:focus {
- text-decoration: none;
- }
-
- .ant-table-layout-fixed table {
- table-layout: auto;
- }
-}
-@media (min-width: 1600px) {
- :global {
- .ant-col-xl-48 {
- width: 20%;
- }
-
- .ant-col-xl-96 {
- width: 40%;
- }
- }
-}
-@media (max-width: 767px) {
- :global {
- .ant-pagination-item,
- .ant-pagination-next,
- .ant-pagination-options,
- .ant-pagination-prev {
- margin-bottom: 8px;
- }
-
- .ant-card {
- .ant-card-head {
- padding: 0 12px;
- }
-
- .ant-card-body {
- padding: 12px;
- }
- }
- }
-}
diff --git a/src/themes/mixin.less b/src/themes/mixin.less
deleted file mode 100644
index 16db875..0000000
--- a/src/themes/mixin.less
+++ /dev/null
@@ -1,35 +0,0 @@
-@import '~themes/default';
-
-@dark-half: #494949;
-@purple: #d897eb;
-@shadow-1: 4px 4px 20px 0 rgba(0, 0, 0, 0.01);
-@shadow-2: 4px 4px 40px 0 rgba(0, 0, 0, 0.05);
-@transition-ease-in: all 0.3s ease-out;
-@transition-ease-out: all 0.3s ease-out;
-@ease-in: ease-in;
-
-.text-overflow {
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
-.text-gradient {
- background-image: -webkit-gradient(
- linear,
- 37.219838% 34.532506%,
- 36.425669% 93.178216%,
- from(#29cdff),
- to(#0a60ff),
- color-stop(0.37, #148eff)
- );
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
-}
-
-.background-hover {
- transition: @transition-ease-in;
- &:hover {
- background-color: @hover-color;
- }
-}
diff --git a/src/themes/vars.less b/src/themes/vars.less
deleted file mode 100644
index b2d50af..0000000
--- a/src/themes/vars.less
+++ /dev/null
@@ -1,2 +0,0 @@
-@import '~themes/default.less';
-@import '~themes/mixin.less';
diff --git a/src/types/admin.ts b/src/types/admin.ts
new file mode 100644
index 0000000..deec2e8
--- /dev/null
+++ b/src/types/admin.ts
@@ -0,0 +1,16 @@
+export type Admin = {
+ id: number;
+ username?: string;
+ real_name?: string;
+ nick_name?: string;
+ mobile?: string;
+ avatar?: string;
+ department?: string;
+ role?: string;
+ status?: number;
+ permissions?: string[] | string;
+ remark?: string;
+};
+
+/** 现已约定后端统一返回 data 字段,调用方直接接收 data */
+export type AdminResponse = Admin | undefined;
diff --git a/src/types/menu.ts b/src/types/menu.ts
new file mode 100644
index 0000000..581ae6e
--- /dev/null
+++ b/src/types/menu.ts
@@ -0,0 +1,25 @@
+export type MenuStatus = 0 | 1;
+
+export type MenuItem = {
+ id: number;
+ title: string;
+ icon?: string;
+ route: string;
+ status: MenuStatus;
+ parentId: number | null;
+ privilege: string[];
+ remark?: string;
+ sort: number;
+ children?: MenuItem[];
+};
+
+export type MenuFormValues = {
+ title: string;
+ icon?: string;
+ route: string;
+ status: MenuStatus;
+ parentId: number | null;
+ privilege: string[];
+ remark?: string;
+ sort: number;
+};
diff --git a/src/types/user.ts b/src/types/user.ts
new file mode 100644
index 0000000..7763e84
--- /dev/null
+++ b/src/types/user.ts
@@ -0,0 +1,19 @@
+export type User = {
+ id: number | string;
+ name?: string;
+ nickName?: string;
+ phone?: string;
+ age?: number;
+ bio?: string;
+ email?: string;
+ address?: string;
+ isMale?: boolean;
+ createTime?: string;
+ lastLogin?: string;
+ status?: 'active' | 'inactive';
+};
+
+export type PagedResponse = {
+ data: T[];
+ total?: number;
+};
diff --git a/src/utils/city.js b/src/utils/city.js
deleted file mode 100644
index 25900d3..0000000
--- a/src/utils/city.js
+++ /dev/null
@@ -1,4055 +0,0 @@
-/*
- ## Address 字典数据
- 字典数据来源 http://www.atatech.org/articles/30028?rnd=254259856
- 国标 省(市)级行政区划码表
- 华北 北京市 天津市 河北省 山西省 内蒙古自治区
- 东北 辽宁省 吉林省 黑龙江省
- 华东 上海市 江苏省 浙江省 安徽省 福建省 江西省 山东省
- 华南 广东省 广西壮族自治区 海南省
- 华中 河南省 湖北省 湖南省
- 西南 重庆市 四川省 贵州省 云南省 西藏自治区
- 西北 陕西省 甘肃省 青海省 宁夏回族自治区 新疆维吾尔自治区
- 港澳台 香港特别行政区 澳门特别行政区 台湾省
-
- **排序**
-
- ```js
- var map = {}
- _.each(_.keys(REGIONS),function(id){
- map[id] = REGIONS[ID]
- })
- JSON.stringify(map)
- ```
-*/
-let DICT = {
- 110000: '北京',
- 110100: '北京市',
- 110101: '东城区',
- 110102: '西城区',
- 110105: '朝阳区',
- 110106: '丰台区',
- 110107: '石景山区',
- 110108: '海淀区',
- 110109: '门头沟区',
- 110111: '房山区',
- 110112: '通州区',
- 110113: '顺义区',
- 110114: '昌平区',
- 110115: '大兴区',
- 110116: '怀柔区',
- 110117: '平谷区',
- 110228: '密云县',
- 110229: '延庆县',
- 110230: '其它区',
- 120000: '天津',
- 120100: '天津市',
- 120101: '和平区',
- 120102: '河东区',
- 120103: '河西区',
- 120104: '南开区',
- 120105: '河北区',
- 120106: '红桥区',
- 120110: '东丽区',
- 120111: '西青区',
- 120112: '津南区',
- 120113: '北辰区',
- 120114: '武清区',
- 120115: '宝坻区',
- 120116: '滨海新区',
- 120221: '宁河县',
- 120223: '静海县',
- 120225: '蓟县',
- 120226: '其它区',
- 130000: '河北省',
- 130100: '石家庄市',
- 130102: '长安区',
- 130103: '桥东区',
- 130104: '桥西区',
- 130105: '新华区',
- 130107: '井陉矿区',
- 130108: '裕华区',
- 130121: '井陉县',
- 130123: '正定县',
- 130124: '栾城县',
- 130125: '行唐县',
- 130126: '灵寿县',
- 130127: '高邑县',
- 130128: '深泽县',
- 130129: '赞皇县',
- 130130: '无极县',
- 130131: '平山县',
- 130132: '元氏县',
- 130133: '赵县',
- 130181: '辛集市',
- 130182: '藁城市',
- 130183: '晋州市',
- 130184: '新乐市',
- 130185: '鹿泉市',
- 130186: '其它区',
- 130200: '唐山市',
- 130202: '路南区',
- 130203: '路北区',
- 130204: '古冶区',
- 130205: '开平区',
- 130207: '丰南区',
- 130208: '丰润区',
- 130223: '滦县',
- 130224: '滦南县',
- 130225: '乐亭县',
- 130227: '迁西县',
- 130229: '玉田县',
- 130230: '曹妃甸区',
- 130281: '遵化市',
- 130283: '迁安市',
- 130284: '其它区',
- 130300: '秦皇岛市',
- 130302: '海港区',
- 130303: '山海关区',
- 130304: '北戴河区',
- 130321: '青龙满族自治县',
- 130322: '昌黎县',
- 130323: '抚宁县',
- 130324: '卢龙县',
- 130398: '其它区',
- 130400: '邯郸市',
- 130402: '邯山区',
- 130403: '丛台区',
- 130404: '复兴区',
- 130406: '峰峰矿区',
- 130421: '邯郸县',
- 130423: '临漳县',
- 130424: '成安县',
- 130425: '大名县',
- 130426: '涉县',
- 130427: '磁县',
- 130428: '肥乡县',
- 130429: '永年县',
- 130430: '邱县',
- 130431: '鸡泽县',
- 130432: '广平县',
- 130433: '馆陶县',
- 130434: '魏县',
- 130435: '曲周县',
- 130481: '武安市',
- 130482: '其它区',
- 130500: '邢台市',
- 130502: '桥东区',
- 130503: '桥西区',
- 130521: '邢台县',
- 130522: '临城县',
- 130523: '内丘县',
- 130524: '柏乡县',
- 130525: '隆尧县',
- 130526: '任县',
- 130527: '南和县',
- 130528: '宁晋县',
- 130529: '巨鹿县',
- 130530: '新河县',
- 130531: '广宗县',
- 130532: '平乡县',
- 130533: '威县',
- 130534: '清河县',
- 130535: '临西县',
- 130581: '南宫市',
- 130582: '沙河市',
- 130583: '其它区',
- 130600: '保定市',
- 130602: '新市区',
- 130603: '北市区',
- 130604: '南市区',
- 130621: '满城县',
- 130622: '清苑县',
- 130623: '涞水县',
- 130624: '阜平县',
- 130625: '徐水县',
- 130626: '定兴县',
- 130627: '唐县',
- 130628: '高阳县',
- 130629: '容城县',
- 130630: '涞源县',
- 130631: '望都县',
- 130632: '安新县',
- 130633: '易县',
- 130634: '曲阳县',
- 130635: '蠡县',
- 130636: '顺平县',
- 130637: '博野县',
- 130638: '雄县',
- 130681: '涿州市',
- 130682: '定州市',
- 130683: '安国市',
- 130684: '高碑店市',
- 130699: '其它区',
- 130700: '张家口市',
- 130702: '桥东区',
- 130703: '桥西区',
- 130705: '宣化区',
- 130706: '下花园区',
- 130721: '宣化县',
- 130722: '张北县',
- 130723: '康保县',
- 130724: '沽源县',
- 130725: '尚义县',
- 130726: '蔚县',
- 130727: '阳原县',
- 130728: '怀安县',
- 130729: '万全县',
- 130730: '怀来县',
- 130731: '涿鹿县',
- 130732: '赤城县',
- 130733: '崇礼县',
- 130734: '其它区',
- 130800: '承德市',
- 130802: '双桥区',
- 130803: '双滦区',
- 130804: '鹰手营子矿区',
- 130821: '承德县',
- 130822: '兴隆县',
- 130823: '平泉县',
- 130824: '滦平县',
- 130825: '隆化县',
- 130826: '丰宁满族自治县',
- 130827: '宽城满族自治县',
- 130828: '围场满族蒙古族自治县',
- 130829: '其它区',
- 130900: '沧州市',
- 130902: '新华区',
- 130903: '运河区',
- 130921: '沧县',
- 130922: '青县',
- 130923: '东光县',
- 130924: '海兴县',
- 130925: '盐山县',
- 130926: '肃宁县',
- 130927: '南皮县',
- 130928: '吴桥县',
- 130929: '献县',
- 130930: '孟村回族自治县',
- 130981: '泊头市',
- 130982: '任丘市',
- 130983: '黄骅市',
- 130984: '河间市',
- 130985: '其它区',
- 131000: '廊坊市',
- 131002: '安次区',
- 131003: '广阳区',
- 131022: '固安县',
- 131023: '永清县',
- 131024: '香河县',
- 131025: '大城县',
- 131026: '文安县',
- 131028: '大厂回族自治县',
- 131081: '霸州市',
- 131082: '三河市',
- 131083: '其它区',
- 131100: '衡水市',
- 131102: '桃城区',
- 131121: '枣强县',
- 131122: '武邑县',
- 131123: '武强县',
- 131124: '饶阳县',
- 131125: '安平县',
- 131126: '故城县',
- 131127: '景县',
- 131128: '阜城县',
- 131181: '冀州市',
- 131182: '深州市',
- 131183: '其它区',
- 140000: '山西省',
- 140100: '太原市',
- 140105: '小店区',
- 140106: '迎泽区',
- 140107: '杏花岭区',
- 140108: '尖草坪区',
- 140109: '万柏林区',
- 140110: '晋源区',
- 140121: '清徐县',
- 140122: '阳曲县',
- 140123: '娄烦县',
- 140181: '古交市',
- 140182: '其它区',
- 140200: '大同市',
- 140202: '城区',
- 140203: '矿区',
- 140211: '南郊区',
- 140212: '新荣区',
- 140221: '阳高县',
- 140222: '天镇县',
- 140223: '广灵县',
- 140224: '灵丘县',
- 140225: '浑源县',
- 140226: '左云县',
- 140227: '大同县',
- 140228: '其它区',
- 140300: '阳泉市',
- 140302: '城区',
- 140303: '矿区',
- 140311: '郊区',
- 140321: '平定县',
- 140322: '盂县',
- 140323: '其它区',
- 140400: '长治市',
- 140421: '长治县',
- 140423: '襄垣县',
- 140424: '屯留县',
- 140425: '平顺县',
- 140426: '黎城县',
- 140427: '壶关县',
- 140428: '长子县',
- 140429: '武乡县',
- 140430: '沁县',
- 140431: '沁源县',
- 140481: '潞城市',
- 140482: '城区',
- 140483: '郊区',
- 140485: '其它区',
- 140500: '晋城市',
- 140502: '城区',
- 140521: '沁水县',
- 140522: '阳城县',
- 140524: '陵川县',
- 140525: '泽州县',
- 140581: '高平市',
- 140582: '其它区',
- 140600: '朔州市',
- 140602: '朔城区',
- 140603: '平鲁区',
- 140621: '山阴县',
- 140622: '应县',
- 140623: '右玉县',
- 140624: '怀仁县',
- 140625: '其它区',
- 140700: '晋中市',
- 140702: '榆次区',
- 140721: '榆社县',
- 140722: '左权县',
- 140723: '和顺县',
- 140724: '昔阳县',
- 140725: '寿阳县',
- 140726: '太谷县',
- 140727: '祁县',
- 140728: '平遥县',
- 140729: '灵石县',
- 140781: '介休市',
- 140782: '其它区',
- 140800: '运城市',
- 140802: '盐湖区',
- 140821: '临猗县',
- 140822: '万荣县',
- 140823: '闻喜县',
- 140824: '稷山县',
- 140825: '新绛县',
- 140826: '绛县',
- 140827: '垣曲县',
- 140828: '夏县',
- 140829: '平陆县',
- 140830: '芮城县',
- 140881: '永济市',
- 140882: '河津市',
- 140883: '其它区',
- 140900: '忻州市',
- 140902: '忻府区',
- 140921: '定襄县',
- 140922: '五台县',
- 140923: '代县',
- 140924: '繁峙县',
- 140925: '宁武县',
- 140926: '静乐县',
- 140927: '神池县',
- 140928: '五寨县',
- 140929: '岢岚县',
- 140930: '河曲县',
- 140931: '保德县',
- 140932: '偏关县',
- 140981: '原平市',
- 140982: '其它区',
- 141000: '临汾市',
- 141002: '尧都区',
- 141021: '曲沃县',
- 141022: '翼城县',
- 141023: '襄汾县',
- 141024: '洪洞县',
- 141025: '古县',
- 141026: '安泽县',
- 141027: '浮山县',
- 141028: '吉县',
- 141029: '乡宁县',
- 141030: '大宁县',
- 141031: '隰县',
- 141032: '永和县',
- 141033: '蒲县',
- 141034: '汾西县',
- 141081: '侯马市',
- 141082: '霍州市',
- 141083: '其它区',
- 141100: '吕梁市',
- 141102: '离石区',
- 141121: '文水县',
- 141122: '交城县',
- 141123: '兴县',
- 141124: '临县',
- 141125: '柳林县',
- 141126: '石楼县',
- 141127: '岚县',
- 141128: '方山县',
- 141129: '中阳县',
- 141130: '交口县',
- 141181: '孝义市',
- 141182: '汾阳市',
- 141183: '其它区',
- 150000: '内蒙古自治区',
- 150100: '呼和浩特市',
- 150102: '新城区',
- 150103: '回民区',
- 150104: '玉泉区',
- 150105: '赛罕区',
- 150121: '土默特左旗',
- 150122: '托克托县',
- 150123: '和林格尔县',
- 150124: '清水河县',
- 150125: '武川县',
- 150126: '其它区',
- 150200: '包头市',
- 150202: '东河区',
- 150203: '昆都仑区',
- 150204: '青山区',
- 150205: '石拐区',
- 150206: '白云鄂博矿区',
- 150207: '九原区',
- 150221: '土默特右旗',
- 150222: '固阳县',
- 150223: '达尔罕茂明安联合旗',
- 150224: '其它区',
- 150300: '乌海市',
- 150302: '海勃湾区',
- 150303: '海南区',
- 150304: '乌达区',
- 150305: '其它区',
- 150400: '赤峰市',
- 150402: '红山区',
- 150403: '元宝山区',
- 150404: '松山区',
- 150421: '阿鲁科尔沁旗',
- 150422: '巴林左旗',
- 150423: '巴林右旗',
- 150424: '林西县',
- 150425: '克什克腾旗',
- 150426: '翁牛特旗',
- 150428: '喀喇沁旗',
- 150429: '宁城县',
- 150430: '敖汉旗',
- 150431: '其它区',
- 150500: '通辽市',
- 150502: '科尔沁区',
- 150521: '科尔沁左翼中旗',
- 150522: '科尔沁左翼后旗',
- 150523: '开鲁县',
- 150524: '库伦旗',
- 150525: '奈曼旗',
- 150526: '扎鲁特旗',
- 150581: '霍林郭勒市',
- 150582: '其它区',
- 150600: '鄂尔多斯市',
- 150602: '东胜区',
- 150621: '达拉特旗',
- 150622: '准格尔旗',
- 150623: '鄂托克前旗',
- 150624: '鄂托克旗',
- 150625: '杭锦旗',
- 150626: '乌审旗',
- 150627: '伊金霍洛旗',
- 150628: '其它区',
- 150700: '呼伦贝尔市',
- 150702: '海拉尔区',
- 150703: '扎赉诺尔区',
- 150721: '阿荣旗',
- 150722: '莫力达瓦达斡尔族自治旗',
- 150723: '鄂伦春自治旗',
- 150724: '鄂温克族自治旗',
- 150725: '陈巴尔虎旗',
- 150726: '新巴尔虎左旗',
- 150727: '新巴尔虎右旗',
- 150781: '满洲里市',
- 150782: '牙克石市',
- 150783: '扎兰屯市',
- 150784: '额尔古纳市',
- 150785: '根河市',
- 150786: '其它区',
- 150800: '巴彦淖尔市',
- 150802: '临河区',
- 150821: '五原县',
- 150822: '磴口县',
- 150823: '乌拉特前旗',
- 150824: '乌拉特中旗',
- 150825: '乌拉特后旗',
- 150826: '杭锦后旗',
- 150827: '其它区',
- 150900: '乌兰察布市',
- 150902: '集宁区',
- 150921: '卓资县',
- 150922: '化德县',
- 150923: '商都县',
- 150924: '兴和县',
- 150925: '凉城县',
- 150926: '察哈尔右翼前旗',
- 150927: '察哈尔右翼中旗',
- 150928: '察哈尔右翼后旗',
- 150929: '四子王旗',
- 150981: '丰镇市',
- 150982: '其它区',
- 152200: '兴安盟',
- 152201: '乌兰浩特市',
- 152202: '阿尔山市',
- 152221: '科尔沁右翼前旗',
- 152222: '科尔沁右翼中旗',
- 152223: '扎赉特旗',
- 152224: '突泉县',
- 152225: '其它区',
- 152500: '锡林郭勒盟',
- 152501: '二连浩特市',
- 152502: '锡林浩特市',
- 152522: '阿巴嘎旗',
- 152523: '苏尼特左旗',
- 152524: '苏尼特右旗',
- 152525: '东乌珠穆沁旗',
- 152526: '西乌珠穆沁旗',
- 152527: '太仆寺旗',
- 152528: '镶黄旗',
- 152529: '正镶白旗',
- 152530: '正蓝旗',
- 152531: '多伦县',
- 152532: '其它区',
- 152900: '阿拉善盟',
- 152921: '阿拉善左旗',
- 152922: '阿拉善右旗',
- 152923: '额济纳旗',
- 152924: '其它区',
- 210000: '辽宁省',
- 210100: '沈阳市',
- 210102: '和平区',
- 210103: '沈河区',
- 210104: '大东区',
- 210105: '皇姑区',
- 210106: '铁西区',
- 210111: '苏家屯区',
- 210112: '东陵区',
- 210113: '新城子区',
- 210114: '于洪区',
- 210122: '辽中县',
- 210123: '康平县',
- 210124: '法库县',
- 210181: '新民市',
- 210184: '沈北新区',
- 210185: '其它区',
- 210200: '大连市',
- 210202: '中山区',
- 210203: '西岗区',
- 210204: '沙河口区',
- 210211: '甘井子区',
- 210212: '旅顺口区',
- 210213: '金州区',
- 210224: '长海县',
- 210281: '瓦房店市',
- 210282: '普兰店市',
- 210283: '庄河市',
- 210298: '其它区',
- 210300: '鞍山市',
- 210302: '铁东区',
- 210303: '铁西区',
- 210304: '立山区',
- 210311: '千山区',
- 210321: '台安县',
- 210323: '岫岩满族自治县',
- 210381: '海城市',
- 210382: '其它区',
- 210400: '抚顺市',
- 210402: '新抚区',
- 210403: '东洲区',
- 210404: '望花区',
- 210411: '顺城区',
- 210421: '抚顺县',
- 210422: '新宾满族自治县',
- 210423: '清原满族自治县',
- 210424: '其它区',
- 210500: '本溪市',
- 210502: '平山区',
- 210503: '溪湖区',
- 210504: '明山区',
- 210505: '南芬区',
- 210521: '本溪满族自治县',
- 210522: '桓仁满族自治县',
- 210523: '其它区',
- 210600: '丹东市',
- 210602: '元宝区',
- 210603: '振兴区',
- 210604: '振安区',
- 210624: '宽甸满族自治县',
- 210681: '东港市',
- 210682: '凤城市',
- 210683: '其它区',
- 210700: '锦州市',
- 210702: '古塔区',
- 210703: '凌河区',
- 210711: '太和区',
- 210726: '黑山县',
- 210727: '义县',
- 210781: '凌海市',
- 210782: '北镇市',
- 210783: '其它区',
- 210800: '营口市',
- 210802: '站前区',
- 210803: '西市区',
- 210804: '鲅鱼圈区',
- 210811: '老边区',
- 210881: '盖州市',
- 210882: '大石桥市',
- 210883: '其它区',
- 210900: '阜新市',
- 210902: '海州区',
- 210903: '新邱区',
- 210904: '太平区',
- 210905: '清河门区',
- 210911: '细河区',
- 210921: '阜新蒙古族自治县',
- 210922: '彰武县',
- 210923: '其它区',
- 211000: '辽阳市',
- 211002: '白塔区',
- 211003: '文圣区',
- 211004: '宏伟区',
- 211005: '弓长岭区',
- 211011: '太子河区',
- 211021: '辽阳县',
- 211081: '灯塔市',
- 211082: '其它区',
- 211100: '盘锦市',
- 211102: '双台子区',
- 211103: '兴隆台区',
- 211121: '大洼县',
- 211122: '盘山县',
- 211123: '其它区',
- 211200: '铁岭市',
- 211202: '银州区',
- 211204: '清河区',
- 211221: '铁岭县',
- 211223: '西丰县',
- 211224: '昌图县',
- 211281: '调兵山市',
- 211282: '开原市',
- 211283: '其它区',
- 211300: '朝阳市',
- 211302: '双塔区',
- 211303: '龙城区',
- 211321: '朝阳县',
- 211322: '建平县',
- 211324: '喀喇沁左翼蒙古族自治县',
- 211381: '北票市',
- 211382: '凌源市',
- 211383: '其它区',
- 211400: '葫芦岛市',
- 211402: '连山区',
- 211403: '龙港区',
- 211404: '南票区',
- 211421: '绥中县',
- 211422: '建昌县',
- 211481: '兴城市',
- 211482: '其它区',
- 220000: '吉林省',
- 220100: '长春市',
- 220102: '南关区',
- 220103: '宽城区',
- 220104: '朝阳区',
- 220105: '二道区',
- 220106: '绿园区',
- 220112: '双阳区',
- 220122: '农安县',
- 220181: '九台市',
- 220182: '榆树市',
- 220183: '德惠市',
- 220188: '其它区',
- 220200: '吉林市',
- 220202: '昌邑区',
- 220203: '龙潭区',
- 220204: '船营区',
- 220211: '丰满区',
- 220221: '永吉县',
- 220281: '蛟河市',
- 220282: '桦甸市',
- 220283: '舒兰市',
- 220284: '磐石市',
- 220285: '其它区',
- 220300: '四平市',
- 220302: '铁西区',
- 220303: '铁东区',
- 220322: '梨树县',
- 220323: '伊通满族自治县',
- 220381: '公主岭市',
- 220382: '双辽市',
- 220383: '其它区',
- 220400: '辽源市',
- 220402: '龙山区',
- 220403: '西安区',
- 220421: '东丰县',
- 220422: '东辽县',
- 220423: '其它区',
- 220500: '通化市',
- 220502: '东昌区',
- 220503: '二道江区',
- 220521: '通化县',
- 220523: '辉南县',
- 220524: '柳河县',
- 220581: '梅河口市',
- 220582: '集安市',
- 220583: '其它区',
- 220600: '白山市',
- 220602: '浑江区',
- 220621: '抚松县',
- 220622: '靖宇县',
- 220623: '长白朝鲜族自治县',
- 220625: '江源区',
- 220681: '临江市',
- 220682: '其它区',
- 220700: '松原市',
- 220702: '宁江区',
- 220721: '前郭尔罗斯蒙古族自治县',
- 220722: '长岭县',
- 220723: '乾安县',
- 220724: '扶余市',
- 220725: '其它区',
- 220800: '白城市',
- 220802: '洮北区',
- 220821: '镇赉县',
- 220822: '通榆县',
- 220881: '洮南市',
- 220882: '大安市',
- 220883: '其它区',
- 222400: '延边朝鲜族自治州',
- 222401: '延吉市',
- 222402: '图们市',
- 222403: '敦化市',
- 222404: '珲春市',
- 222405: '龙井市',
- 222406: '和龙市',
- 222424: '汪清县',
- 222426: '安图县',
- 222427: '其它区',
- 230000: '黑龙江省',
- 230100: '哈尔滨市',
- 230102: '道里区',
- 230103: '南岗区',
- 230104: '道外区',
- 230106: '香坊区',
- 230108: '平房区',
- 230109: '松北区',
- 230111: '呼兰区',
- 230123: '依兰县',
- 230124: '方正县',
- 230125: '宾县',
- 230126: '巴彦县',
- 230127: '木兰县',
- 230128: '通河县',
- 230129: '延寿县',
- 230181: '阿城区',
- 230182: '双城市',
- 230183: '尚志市',
- 230184: '五常市',
- 230186: '其它区',
- 230200: '齐齐哈尔市',
- 230202: '龙沙区',
- 230203: '建华区',
- 230204: '铁锋区',
- 230205: '昂昂溪区',
- 230206: '富拉尔基区',
- 230207: '碾子山区',
- 230208: '梅里斯达斡尔族区',
- 230221: '龙江县',
- 230223: '依安县',
- 230224: '泰来县',
- 230225: '甘南县',
- 230227: '富裕县',
- 230229: '克山县',
- 230230: '克东县',
- 230231: '拜泉县',
- 230281: '讷河市',
- 230282: '其它区',
- 230300: '鸡西市',
- 230302: '鸡冠区',
- 230303: '恒山区',
- 230304: '滴道区',
- 230305: '梨树区',
- 230306: '城子河区',
- 230307: '麻山区',
- 230321: '鸡东县',
- 230381: '虎林市',
- 230382: '密山市',
- 230383: '其它区',
- 230400: '鹤岗市',
- 230402: '向阳区',
- 230403: '工农区',
- 230404: '南山区',
- 230405: '兴安区',
- 230406: '东山区',
- 230407: '兴山区',
- 230421: '萝北县',
- 230422: '绥滨县',
- 230423: '其它区',
- 230500: '双鸭山市',
- 230502: '尖山区',
- 230503: '岭东区',
- 230505: '四方台区',
- 230506: '宝山区',
- 230521: '集贤县',
- 230522: '友谊县',
- 230523: '宝清县',
- 230524: '饶河县',
- 230525: '其它区',
- 230600: '大庆市',
- 230602: '萨尔图区',
- 230603: '龙凤区',
- 230604: '让胡路区',
- 230605: '红岗区',
- 230606: '大同区',
- 230621: '肇州县',
- 230622: '肇源县',
- 230623: '林甸县',
- 230624: '杜尔伯特蒙古族自治县',
- 230625: '其它区',
- 230700: '伊春市',
- 230702: '伊春区',
- 230703: '南岔区',
- 230704: '友好区',
- 230705: '西林区',
- 230706: '翠峦区',
- 230707: '新青区',
- 230708: '美溪区',
- 230709: '金山屯区',
- 230710: '五营区',
- 230711: '乌马河区',
- 230712: '汤旺河区',
- 230713: '带岭区',
- 230714: '乌伊岭区',
- 230715: '红星区',
- 230716: '上甘岭区',
- 230722: '嘉荫县',
- 230781: '铁力市',
- 230782: '其它区',
- 230800: '佳木斯市',
- 230803: '向阳区',
- 230804: '前进区',
- 230805: '东风区',
- 230811: '郊区',
- 230822: '桦南县',
- 230826: '桦川县',
- 230828: '汤原县',
- 230833: '抚远县',
- 230881: '同江市',
- 230882: '富锦市',
- 230883: '其它区',
- 230900: '七台河市',
- 230902: '新兴区',
- 230903: '桃山区',
- 230904: '茄子河区',
- 230921: '勃利县',
- 230922: '其它区',
- 231000: '牡丹江市',
- 231002: '东安区',
- 231003: '阳明区',
- 231004: '爱民区',
- 231005: '西安区',
- 231024: '东宁县',
- 231025: '林口县',
- 231081: '绥芬河市',
- 231083: '海林市',
- 231084: '宁安市',
- 231085: '穆棱市',
- 231086: '其它区',
- 231100: '黑河市',
- 231102: '爱辉区',
- 231121: '嫩江县',
- 231123: '逊克县',
- 231124: '孙吴县',
- 231181: '北安市',
- 231182: '五大连池市',
- 231183: '其它区',
- 231200: '绥化市',
- 231202: '北林区',
- 231221: '望奎县',
- 231222: '兰西县',
- 231223: '青冈县',
- 231224: '庆安县',
- 231225: '明水县',
- 231226: '绥棱县',
- 231281: '安达市',
- 231282: '肇东市',
- 231283: '海伦市',
- 231284: '其它区',
- 232700: '大兴安岭地区',
- 232702: '松岭区',
- 232703: '新林区',
- 232704: '呼中区',
- 232721: '呼玛县',
- 232722: '塔河县',
- 232723: '漠河县',
- 232724: '加格达奇区',
- 232725: '其它区',
- 310000: '上海',
- 310100: '上海市',
- 310101: '黄浦区',
- 310104: '徐汇区',
- 310105: '长宁区',
- 310106: '静安区',
- 310107: '普陀区',
- 310108: '闸北区',
- 310109: '虹口区',
- 310110: '杨浦区',
- 310112: '闵行区',
- 310113: '宝山区',
- 310114: '嘉定区',
- 310115: '浦东新区',
- 310116: '金山区',
- 310117: '松江区',
- 310118: '青浦区',
- 310120: '奉贤区',
- 310230: '崇明县',
- 310231: '其它区',
- 320000: '江苏省',
- 320100: '南京市',
- 320102: '玄武区',
- 320104: '秦淮区',
- 320105: '建邺区',
- 320106: '鼓楼区',
- 320111: '浦口区',
- 320113: '栖霞区',
- 320114: '雨花台区',
- 320115: '江宁区',
- 320116: '六合区',
- 320124: '溧水区',
- 320125: '高淳区',
- 320126: '其它区',
- 320200: '无锡市',
- 320202: '崇安区',
- 320203: '南长区',
- 320204: '北塘区',
- 320205: '锡山区',
- 320206: '惠山区',
- 320211: '滨湖区',
- 320281: '江阴市',
- 320282: '宜兴市',
- 320297: '其它区',
- 320300: '徐州市',
- 320302: '鼓楼区',
- 320303: '云龙区',
- 320305: '贾汪区',
- 320311: '泉山区',
- 320321: '丰县',
- 320322: '沛县',
- 320323: '铜山区',
- 320324: '睢宁县',
- 320381: '新沂市',
- 320382: '邳州市',
- 320383: '其它区',
- 320400: '常州市',
- 320402: '天宁区',
- 320404: '钟楼区',
- 320405: '戚墅堰区',
- 320411: '新北区',
- 320412: '武进区',
- 320481: '溧阳市',
- 320482: '金坛市',
- 320483: '其它区',
- 320500: '苏州市',
- 320505: '虎丘区',
- 320506: '吴中区',
- 320507: '相城区',
- 320508: '姑苏区',
- 320581: '常熟市',
- 320582: '张家港市',
- 320583: '昆山市',
- 320584: '吴江区',
- 320585: '太仓市',
- 320596: '其它区',
- 320600: '南通市',
- 320602: '崇川区',
- 320611: '港闸区',
- 320612: '通州区',
- 320621: '海安县',
- 320623: '如东县',
- 320681: '启东市',
- 320682: '如皋市',
- 320684: '海门市',
- 320694: '其它区',
- 320700: '连云港市',
- 320703: '连云区',
- 320705: '新浦区',
- 320706: '海州区',
- 320721: '赣榆县',
- 320722: '东海县',
- 320723: '灌云县',
- 320724: '灌南县',
- 320725: '其它区',
- 320800: '淮安市',
- 320802: '清河区',
- 320803: '淮安区',
- 320804: '淮阴区',
- 320811: '清浦区',
- 320826: '涟水县',
- 320829: '洪泽县',
- 320830: '盱眙县',
- 320831: '金湖县',
- 320832: '其它区',
- 320900: '盐城市',
- 320902: '亭湖区',
- 320903: '盐都区',
- 320921: '响水县',
- 320922: '滨海县',
- 320923: '阜宁县',
- 320924: '射阳县',
- 320925: '建湖县',
- 320981: '东台市',
- 320982: '大丰市',
- 320983: '其它区',
- 321000: '扬州市',
- 321002: '广陵区',
- 321003: '邗江区',
- 321023: '宝应县',
- 321081: '仪征市',
- 321084: '高邮市',
- 321088: '江都区',
- 321093: '其它区',
- 321100: '镇江市',
- 321102: '京口区',
- 321111: '润州区',
- 321112: '丹徒区',
- 321181: '丹阳市',
- 321182: '扬中市',
- 321183: '句容市',
- 321184: '其它区',
- 321200: '泰州市',
- 321202: '海陵区',
- 321203: '高港区',
- 321281: '兴化市',
- 321282: '靖江市',
- 321283: '泰兴市',
- 321284: '姜堰区',
- 321285: '其它区',
- 321300: '宿迁市',
- 321302: '宿城区',
- 321311: '宿豫区',
- 321322: '沭阳县',
- 321323: '泗阳县',
- 321324: '泗洪县',
- 321325: '其它区',
- 330000: '浙江省',
- 330100: '杭州市',
- 330102: '上城区',
- 330103: '下城区',
- 330104: '江干区',
- 330105: '拱墅区',
- 330106: '西湖区',
- 330108: '滨江区',
- 330109: '萧山区',
- 330110: '余杭区',
- 330122: '桐庐县',
- 330127: '淳安县',
- 330182: '建德市',
- 330183: '富阳市',
- 330185: '临安市',
- 330186: '其它区',
- 330200: '宁波市',
- 330203: '海曙区',
- 330204: '江东区',
- 330205: '江北区',
- 330206: '北仑区',
- 330211: '镇海区',
- 330212: '鄞州区',
- 330225: '象山县',
- 330226: '宁海县',
- 330281: '余姚市',
- 330282: '慈溪市',
- 330283: '奉化市',
- 330284: '其它区',
- 330300: '温州市',
- 330302: '鹿城区',
- 330303: '龙湾区',
- 330304: '瓯海区',
- 330322: '洞头县',
- 330324: '永嘉县',
- 330326: '平阳县',
- 330327: '苍南县',
- 330328: '文成县',
- 330329: '泰顺县',
- 330381: '瑞安市',
- 330382: '乐清市',
- 330383: '其它区',
- 330400: '嘉兴市',
- 330402: '南湖区',
- 330411: '秀洲区',
- 330421: '嘉善县',
- 330424: '海盐县',
- 330481: '海宁市',
- 330482: '平湖市',
- 330483: '桐乡市',
- 330484: '其它区',
- 330500: '湖州市',
- 330502: '吴兴区',
- 330503: '南浔区',
- 330521: '德清县',
- 330522: '长兴县',
- 330523: '安吉县',
- 330524: '其它区',
- 330600: '绍兴市',
- 330602: '越城区',
- 330621: '绍兴县',
- 330624: '新昌县',
- 330681: '诸暨市',
- 330682: '上虞市',
- 330683: '嵊州市',
- 330684: '其它区',
- 330700: '金华市',
- 330702: '婺城区',
- 330703: '金东区',
- 330723: '武义县',
- 330726: '浦江县',
- 330727: '磐安县',
- 330781: '兰溪市',
- 330782: '义乌市',
- 330783: '东阳市',
- 330784: '永康市',
- 330785: '其它区',
- 330800: '衢州市',
- 330802: '柯城区',
- 330803: '衢江区',
- 330822: '常山县',
- 330824: '开化县',
- 330825: '龙游县',
- 330881: '江山市',
- 330882: '其它区',
- 330900: '舟山市',
- 330902: '定海区',
- 330903: '普陀区',
- 330921: '岱山县',
- 330922: '嵊泗县',
- 330923: '其它区',
- 331000: '台州市',
- 331002: '椒江区',
- 331003: '黄岩区',
- 331004: '路桥区',
- 331021: '玉环县',
- 331022: '三门县',
- 331023: '天台县',
- 331024: '仙居县',
- 331081: '温岭市',
- 331082: '临海市',
- 331083: '其它区',
- 331100: '丽水市',
- 331102: '莲都区',
- 331121: '青田县',
- 331122: '缙云县',
- 331123: '遂昌县',
- 331124: '松阳县',
- 331125: '云和县',
- 331126: '庆元县',
- 331127: '景宁畲族自治县',
- 331181: '龙泉市',
- 331182: '其它区',
- 340000: '安徽省',
- 340100: '合肥市',
- 340102: '瑶海区',
- 340103: '庐阳区',
- 340104: '蜀山区',
- 340111: '包河区',
- 340121: '长丰县',
- 340122: '肥东县',
- 340123: '肥西县',
- 340192: '其它区',
- 340200: '芜湖市',
- 340202: '镜湖区',
- 340203: '弋江区',
- 340207: '鸠江区',
- 340208: '三山区',
- 340221: '芜湖县',
- 340222: '繁昌县',
- 340223: '南陵县',
- 340224: '其它区',
- 340300: '蚌埠市',
- 340302: '龙子湖区',
- 340303: '蚌山区',
- 340304: '禹会区',
- 340311: '淮上区',
- 340321: '怀远县',
- 340322: '五河县',
- 340323: '固镇县',
- 340324: '其它区',
- 340400: '淮南市',
- 340402: '大通区',
- 340403: '田家庵区',
- 340404: '谢家集区',
- 340405: '八公山区',
- 340406: '潘集区',
- 340421: '凤台县',
- 340422: '其它区',
- 340500: '马鞍山市',
- 340503: '花山区',
- 340504: '雨山区',
- 340506: '博望区',
- 340521: '当涂县',
- 340522: '其它区',
- 340600: '淮北市',
- 340602: '杜集区',
- 340603: '相山区',
- 340604: '烈山区',
- 340621: '濉溪县',
- 340622: '其它区',
- 340700: '铜陵市',
- 340702: '铜官山区',
- 340703: '狮子山区',
- 340711: '郊区',
- 340721: '铜陵县',
- 340722: '其它区',
- 340800: '安庆市',
- 340802: '迎江区',
- 340803: '大观区',
- 340811: '宜秀区',
- 340822: '怀宁县',
- 340823: '枞阳县',
- 340824: '潜山县',
- 340825: '太湖县',
- 340826: '宿松县',
- 340827: '望江县',
- 340828: '岳西县',
- 340881: '桐城市',
- 340882: '其它区',
- 341000: '黄山市',
- 341002: '屯溪区',
- 341003: '黄山区',
- 341004: '徽州区',
- 341021: '歙县',
- 341022: '休宁县',
- 341023: '黟县',
- 341024: '祁门县',
- 341025: '其它区',
- 341100: '滁州市',
- 341102: '琅琊区',
- 341103: '南谯区',
- 341122: '来安县',
- 341124: '全椒县',
- 341125: '定远县',
- 341126: '凤阳县',
- 341181: '天长市',
- 341182: '明光市',
- 341183: '其它区',
- 341200: '阜阳市',
- 341202: '颍州区',
- 341203: '颍东区',
- 341204: '颍泉区',
- 341221: '临泉县',
- 341222: '太和县',
- 341225: '阜南县',
- 341226: '颍上县',
- 341282: '界首市',
- 341283: '其它区',
- 341300: '宿州市',
- 341302: '埇桥区',
- 341321: '砀山县',
- 341322: '萧县',
- 341323: '灵璧县',
- 341324: '泗县',
- 341325: '其它区',
- 341400: '巢湖市',
- 341421: '庐江县',
- 341422: '无为县',
- 341423: '含山县',
- 341424: '和县',
- 341500: '六安市',
- 341502: '金安区',
- 341503: '裕安区',
- 341521: '寿县',
- 341522: '霍邱县',
- 341523: '舒城县',
- 341524: '金寨县',
- 341525: '霍山县',
- 341526: '其它区',
- 341600: '亳州市',
- 341602: '谯城区',
- 341621: '涡阳县',
- 341622: '蒙城县',
- 341623: '利辛县',
- 341624: '其它区',
- 341700: '池州市',
- 341702: '贵池区',
- 341721: '东至县',
- 341722: '石台县',
- 341723: '青阳县',
- 341724: '其它区',
- 341800: '宣城市',
- 341802: '宣州区',
- 341821: '郎溪县',
- 341822: '广德县',
- 341823: '泾县',
- 341824: '绩溪县',
- 341825: '旌德县',
- 341881: '宁国市',
- 341882: '其它区',
- 350000: '福建省',
- 350100: '福州市',
- 350102: '鼓楼区',
- 350103: '台江区',
- 350104: '仓山区',
- 350105: '马尾区',
- 350111: '晋安区',
- 350121: '闽侯县',
- 350122: '连江县',
- 350123: '罗源县',
- 350124: '闽清县',
- 350125: '永泰县',
- 350128: '平潭县',
- 350181: '福清市',
- 350182: '长乐市',
- 350183: '其它区',
- 350200: '厦门市',
- 350203: '思明区',
- 350205: '海沧区',
- 350206: '湖里区',
- 350211: '集美区',
- 350212: '同安区',
- 350213: '翔安区',
- 350214: '其它区',
- 350300: '莆田市',
- 350302: '城厢区',
- 350303: '涵江区',
- 350304: '荔城区',
- 350305: '秀屿区',
- 350322: '仙游县',
- 350323: '其它区',
- 350400: '三明市',
- 350402: '梅列区',
- 350403: '三元区',
- 350421: '明溪县',
- 350423: '清流县',
- 350424: '宁化县',
- 350425: '大田县',
- 350426: '尤溪县',
- 350427: '沙县',
- 350428: '将乐县',
- 350429: '泰宁县',
- 350430: '建宁县',
- 350481: '永安市',
- 350482: '其它区',
- 350500: '泉州市',
- 350502: '鲤城区',
- 350503: '丰泽区',
- 350504: '洛江区',
- 350505: '泉港区',
- 350521: '惠安县',
- 350524: '安溪县',
- 350525: '永春县',
- 350526: '德化县',
- 350527: '金门县',
- 350581: '石狮市',
- 350582: '晋江市',
- 350583: '南安市',
- 350584: '其它区',
- 350600: '漳州市',
- 350602: '芗城区',
- 350603: '龙文区',
- 350622: '云霄县',
- 350623: '漳浦县',
- 350624: '诏安县',
- 350625: '长泰县',
- 350626: '东山县',
- 350627: '南靖县',
- 350628: '平和县',
- 350629: '华安县',
- 350681: '龙海市',
- 350682: '其它区',
- 350700: '南平市',
- 350702: '延平区',
- 350721: '顺昌县',
- 350722: '浦城县',
- 350723: '光泽县',
- 350724: '松溪县',
- 350725: '政和县',
- 350781: '邵武市',
- 350782: '武夷山市',
- 350783: '建瓯市',
- 350784: '建阳市',
- 350785: '其它区',
- 350800: '龙岩市',
- 350802: '新罗区',
- 350821: '长汀县',
- 350822: '永定县',
- 350823: '上杭县',
- 350824: '武平县',
- 350825: '连城县',
- 350881: '漳平市',
- 350882: '其它区',
- 350900: '宁德市',
- 350902: '蕉城区',
- 350921: '霞浦县',
- 350922: '古田县',
- 350923: '屏南县',
- 350924: '寿宁县',
- 350925: '周宁县',
- 350926: '柘荣县',
- 350981: '福安市',
- 350982: '福鼎市',
- 350983: '其它区',
- 360000: '江西省',
- 360100: '南昌市',
- 360102: '东湖区',
- 360103: '西湖区',
- 360104: '青云谱区',
- 360105: '湾里区',
- 360111: '青山湖区',
- 360121: '南昌县',
- 360122: '新建县',
- 360123: '安义县',
- 360124: '进贤县',
- 360128: '其它区',
- 360200: '景德镇市',
- 360202: '昌江区',
- 360203: '珠山区',
- 360222: '浮梁县',
- 360281: '乐平市',
- 360282: '其它区',
- 360300: '萍乡市',
- 360302: '安源区',
- 360313: '湘东区',
- 360321: '莲花县',
- 360322: '上栗县',
- 360323: '芦溪县',
- 360324: '其它区',
- 360400: '九江市',
- 360402: '庐山区',
- 360403: '浔阳区',
- 360421: '九江县',
- 360423: '武宁县',
- 360424: '修水县',
- 360425: '永修县',
- 360426: '德安县',
- 360427: '星子县',
- 360428: '都昌县',
- 360429: '湖口县',
- 360430: '彭泽县',
- 360481: '瑞昌市',
- 360482: '其它区',
- 360483: '共青城市',
- 360500: '新余市',
- 360502: '渝水区',
- 360521: '分宜县',
- 360522: '其它区',
- 360600: '鹰潭市',
- 360602: '月湖区',
- 360622: '余江县',
- 360681: '贵溪市',
- 360682: '其它区',
- 360700: '赣州市',
- 360702: '章贡区',
- 360721: '赣县',
- 360722: '信丰县',
- 360723: '大余县',
- 360724: '上犹县',
- 360725: '崇义县',
- 360726: '安远县',
- 360727: '龙南县',
- 360728: '定南县',
- 360729: '全南县',
- 360730: '宁都县',
- 360731: '于都县',
- 360732: '兴国县',
- 360733: '会昌县',
- 360734: '寻乌县',
- 360735: '石城县',
- 360781: '瑞金市',
- 360782: '南康市',
- 360783: '其它区',
- 360800: '吉安市',
- 360802: '吉州区',
- 360803: '青原区',
- 360821: '吉安县',
- 360822: '吉水县',
- 360823: '峡江县',
- 360824: '新干县',
- 360825: '永丰县',
- 360826: '泰和县',
- 360827: '遂川县',
- 360828: '万安县',
- 360829: '安福县',
- 360830: '永新县',
- 360881: '井冈山市',
- 360882: '其它区',
- 360900: '宜春市',
- 360902: '袁州区',
- 360921: '奉新县',
- 360922: '万载县',
- 360923: '上高县',
- 360924: '宜丰县',
- 360925: '靖安县',
- 360926: '铜鼓县',
- 360981: '丰城市',
- 360982: '樟树市',
- 360983: '高安市',
- 360984: '其它区',
- 361000: '抚州市',
- 361002: '临川区',
- 361021: '南城县',
- 361022: '黎川县',
- 361023: '南丰县',
- 361024: '崇仁县',
- 361025: '乐安县',
- 361026: '宜黄县',
- 361027: '金溪县',
- 361028: '资溪县',
- 361029: '东乡县',
- 361030: '广昌县',
- 361031: '其它区',
- 361100: '上饶市',
- 361102: '信州区',
- 361121: '上饶县',
- 361122: '广丰县',
- 361123: '玉山县',
- 361124: '铅山县',
- 361125: '横峰县',
- 361126: '弋阳县',
- 361127: '余干县',
- 361128: '鄱阳县',
- 361129: '万年县',
- 361130: '婺源县',
- 361181: '德兴市',
- 361182: '其它区',
- 370000: '山东省',
- 370100: '济南市',
- 370102: '历下区',
- 370103: '市中区',
- 370104: '槐荫区',
- 370105: '天桥区',
- 370112: '历城区',
- 370113: '长清区',
- 370124: '平阴县',
- 370125: '济阳县',
- 370126: '商河县',
- 370181: '章丘市',
- 370182: '其它区',
- 370200: '青岛市',
- 370202: '市南区',
- 370203: '市北区',
- 370211: '黄岛区',
- 370212: '崂山区',
- 370213: '李沧区',
- 370214: '城阳区',
- 370281: '胶州市',
- 370282: '即墨市',
- 370283: '平度市',
- 370285: '莱西市',
- 370286: '其它区',
- 370300: '淄博市',
- 370302: '淄川区',
- 370303: '张店区',
- 370304: '博山区',
- 370305: '临淄区',
- 370306: '周村区',
- 370321: '桓台县',
- 370322: '高青县',
- 370323: '沂源县',
- 370324: '其它区',
- 370400: '枣庄市',
- 370402: '市中区',
- 370403: '薛城区',
- 370404: '峄城区',
- 370405: '台儿庄区',
- 370406: '山亭区',
- 370481: '滕州市',
- 370482: '其它区',
- 370500: '东营市',
- 370502: '东营区',
- 370503: '河口区',
- 370521: '垦利县',
- 370522: '利津县',
- 370523: '广饶县',
- 370591: '其它区',
- 370600: '烟台市',
- 370602: '芝罘区',
- 370611: '福山区',
- 370612: '牟平区',
- 370613: '莱山区',
- 370634: '长岛县',
- 370681: '龙口市',
- 370682: '莱阳市',
- 370683: '莱州市',
- 370684: '蓬莱市',
- 370685: '招远市',
- 370686: '栖霞市',
- 370687: '海阳市',
- 370688: '其它区',
- 370700: '潍坊市',
- 370702: '潍城区',
- 370703: '寒亭区',
- 370704: '坊子区',
- 370705: '奎文区',
- 370724: '临朐县',
- 370725: '昌乐县',
- 370781: '青州市',
- 370782: '诸城市',
- 370783: '寿光市',
- 370784: '安丘市',
- 370785: '高密市',
- 370786: '昌邑市',
- 370787: '其它区',
- 370800: '济宁市',
- 370802: '市中区',
- 370811: '任城区',
- 370826: '微山县',
- 370827: '鱼台县',
- 370828: '金乡县',
- 370829: '嘉祥县',
- 370830: '汶上县',
- 370831: '泗水县',
- 370832: '梁山县',
- 370881: '曲阜市',
- 370882: '兖州市',
- 370883: '邹城市',
- 370884: '其它区',
- 370900: '泰安市',
- 370902: '泰山区',
- 370903: '岱岳区',
- 370921: '宁阳县',
- 370923: '东平县',
- 370982: '新泰市',
- 370983: '肥城市',
- 370984: '其它区',
- 371000: '威海市',
- 371002: '环翠区',
- 371081: '文登市',
- 371082: '荣成市',
- 371083: '乳山市',
- 371084: '其它区',
- 371100: '日照市',
- 371102: '东港区',
- 371103: '岚山区',
- 371121: '五莲县',
- 371122: '莒县',
- 371123: '其它区',
- 371200: '莱芜市',
- 371202: '莱城区',
- 371203: '钢城区',
- 371204: '其它区',
- 371300: '临沂市',
- 371302: '兰山区',
- 371311: '罗庄区',
- 371312: '河东区',
- 371321: '沂南县',
- 371322: '郯城县',
- 371323: '沂水县',
- 371324: '苍山县',
- 371325: '费县',
- 371326: '平邑县',
- 371327: '莒南县',
- 371328: '蒙阴县',
- 371329: '临沭县',
- 371330: '其它区',
- 371400: '德州市',
- 371402: '德城区',
- 371421: '陵县',
- 371422: '宁津县',
- 371423: '庆云县',
- 371424: '临邑县',
- 371425: '齐河县',
- 371426: '平原县',
- 371427: '夏津县',
- 371428: '武城县',
- 371481: '乐陵市',
- 371482: '禹城市',
- 371483: '其它区',
- 371500: '聊城市',
- 371502: '东昌府区',
- 371521: '阳谷县',
- 371522: '莘县',
- 371523: '茌平县',
- 371524: '东阿县',
- 371525: '冠县',
- 371526: '高唐县',
- 371581: '临清市',
- 371582: '其它区',
- 371600: '滨州市',
- 371602: '滨城区',
- 371621: '惠民县',
- 371622: '阳信县',
- 371623: '无棣县',
- 371624: '沾化县',
- 371625: '博兴县',
- 371626: '邹平县',
- 371627: '其它区',
- 371700: '菏泽市',
- 371702: '牡丹区',
- 371721: '曹县',
- 371722: '单县',
- 371723: '成武县',
- 371724: '巨野县',
- 371725: '郓城县',
- 371726: '鄄城县',
- 371727: '定陶县',
- 371728: '东明县',
- 371729: '其它区',
- 410000: '河南省',
- 410100: '郑州市',
- 410102: '中原区',
- 410103: '二七区',
- 410104: '管城回族区',
- 410105: '金水区',
- 410106: '上街区',
- 410108: '惠济区',
- 410122: '中牟县',
- 410181: '巩义市',
- 410182: '荥阳市',
- 410183: '新密市',
- 410184: '新郑市',
- 410185: '登封市',
- 410188: '其它区',
- 410200: '开封市',
- 410202: '龙亭区',
- 410203: '顺河回族区',
- 410204: '鼓楼区',
- 410205: '禹王台区',
- 410211: '金明区',
- 410221: '杞县',
- 410222: '通许县',
- 410223: '尉氏县',
- 410224: '开封县',
- 410225: '兰考县',
- 410226: '其它区',
- 410300: '洛阳市',
- 410302: '老城区',
- 410303: '西工区',
- 410304: '瀍河回族区',
- 410305: '涧西区',
- 410306: '吉利区',
- 410307: '洛龙区',
- 410322: '孟津县',
- 410323: '新安县',
- 410324: '栾川县',
- 410325: '嵩县',
- 410326: '汝阳县',
- 410327: '宜阳县',
- 410328: '洛宁县',
- 410329: '伊川县',
- 410381: '偃师市',
- 410400: '平顶山市',
- 410402: '新华区',
- 410403: '卫东区',
- 410404: '石龙区',
- 410411: '湛河区',
- 410421: '宝丰县',
- 410422: '叶县',
- 410423: '鲁山县',
- 410425: '郏县',
- 410481: '舞钢市',
- 410482: '汝州市',
- 410483: '其它区',
- 410500: '安阳市',
- 410502: '文峰区',
- 410503: '北关区',
- 410505: '殷都区',
- 410506: '龙安区',
- 410522: '安阳县',
- 410523: '汤阴县',
- 410526: '滑县',
- 410527: '内黄县',
- 410581: '林州市',
- 410582: '其它区',
- 410600: '鹤壁市',
- 410602: '鹤山区',
- 410603: '山城区',
- 410611: '淇滨区',
- 410621: '浚县',
- 410622: '淇县',
- 410623: '其它区',
- 410700: '新乡市',
- 410702: '红旗区',
- 410703: '卫滨区',
- 410704: '凤泉区',
- 410711: '牧野区',
- 410721: '新乡县',
- 410724: '获嘉县',
- 410725: '原阳县',
- 410726: '延津县',
- 410727: '封丘县',
- 410728: '长垣县',
- 410781: '卫辉市',
- 410782: '辉县市',
- 410783: '其它区',
- 410800: '焦作市',
- 410802: '解放区',
- 410803: '中站区',
- 410804: '马村区',
- 410811: '山阳区',
- 410821: '修武县',
- 410822: '博爱县',
- 410823: '武陟县',
- 410825: '温县',
- 410881: '济源市',
- 410882: '沁阳市',
- 410883: '孟州市',
- 410884: '其它区',
- 410900: '濮阳市',
- 410902: '华龙区',
- 410922: '清丰县',
- 410923: '南乐县',
- 410926: '范县',
- 410927: '台前县',
- 410928: '濮阳县',
- 410929: '其它区',
- 411000: '许昌市',
- 411002: '魏都区',
- 411023: '许昌县',
- 411024: '鄢陵县',
- 411025: '襄城县',
- 411081: '禹州市',
- 411082: '长葛市',
- 411083: '其它区',
- 411100: '漯河市',
- 411102: '源汇区',
- 411103: '郾城区',
- 411104: '召陵区',
- 411121: '舞阳县',
- 411122: '临颍县',
- 411123: '其它区',
- 411200: '三门峡市',
- 411202: '湖滨区',
- 411221: '渑池县',
- 411222: '陕县',
- 411224: '卢氏县',
- 411281: '义马市',
- 411282: '灵宝市',
- 411283: '其它区',
- 411300: '南阳市',
- 411302: '宛城区',
- 411303: '卧龙区',
- 411321: '南召县',
- 411322: '方城县',
- 411323: '西峡县',
- 411324: '镇平县',
- 411325: '内乡县',
- 411326: '淅川县',
- 411327: '社旗县',
- 411328: '唐河县',
- 411329: '新野县',
- 411330: '桐柏县',
- 411381: '邓州市',
- 411382: '其它区',
- 411400: '商丘市',
- 411402: '梁园区',
- 411403: '睢阳区',
- 411421: '民权县',
- 411422: '睢县',
- 411423: '宁陵县',
- 411424: '柘城县',
- 411425: '虞城县',
- 411426: '夏邑县',
- 411481: '永城市',
- 411482: '其它区',
- 411500: '信阳市',
- 411502: '浉河区',
- 411503: '平桥区',
- 411521: '罗山县',
- 411522: '光山县',
- 411523: '新县',
- 411524: '商城县',
- 411525: '固始县',
- 411526: '潢川县',
- 411527: '淮滨县',
- 411528: '息县',
- 411529: '其它区',
- 411600: '周口市',
- 411602: '川汇区',
- 411621: '扶沟县',
- 411622: '西华县',
- 411623: '商水县',
- 411624: '沈丘县',
- 411625: '郸城县',
- 411626: '淮阳县',
- 411627: '太康县',
- 411628: '鹿邑县',
- 411681: '项城市',
- 411682: '其它区',
- 411700: '驻马店市',
- 411702: '驿城区',
- 411721: '西平县',
- 411722: '上蔡县',
- 411723: '平舆县',
- 411724: '正阳县',
- 411725: '确山县',
- 411726: '泌阳县',
- 411727: '汝南县',
- 411728: '遂平县',
- 411729: '新蔡县',
- 411730: '其它区',
- 420000: '湖北省',
- 420100: '武汉市',
- 420102: '江岸区',
- 420103: '江汉区',
- 420104: '硚口区',
- 420105: '汉阳区',
- 420106: '武昌区',
- 420107: '青山区',
- 420111: '洪山区',
- 420112: '东西湖区',
- 420113: '汉南区',
- 420114: '蔡甸区',
- 420115: '江夏区',
- 420116: '黄陂区',
- 420117: '新洲区',
- 420118: '其它区',
- 420200: '黄石市',
- 420202: '黄石港区',
- 420203: '西塞山区',
- 420204: '下陆区',
- 420205: '铁山区',
- 420222: '阳新县',
- 420281: '大冶市',
- 420282: '其它区',
- 420300: '十堰市',
- 420302: '茅箭区',
- 420303: '张湾区',
- 420321: '郧县',
- 420322: '郧西县',
- 420323: '竹山县',
- 420324: '竹溪县',
- 420325: '房县',
- 420381: '丹江口市',
- 420383: '其它区',
- 420500: '宜昌市',
- 420502: '西陵区',
- 420503: '伍家岗区',
- 420504: '点军区',
- 420505: '猇亭区',
- 420506: '夷陵区',
- 420525: '远安县',
- 420526: '兴山县',
- 420527: '秭归县',
- 420528: '长阳土家族自治县',
- 420529: '五峰土家族自治县',
- 420581: '宜都市',
- 420582: '当阳市',
- 420583: '枝江市',
- 420584: '其它区',
- 420600: '襄阳市',
- 420602: '襄城区',
- 420606: '樊城区',
- 420607: '襄州区',
- 420624: '南漳县',
- 420625: '谷城县',
- 420626: '保康县',
- 420682: '老河口市',
- 420683: '枣阳市',
- 420684: '宜城市',
- 420685: '其它区',
- 420700: '鄂州市',
- 420702: '梁子湖区',
- 420703: '华容区',
- 420704: '鄂城区',
- 420705: '其它区',
- 420800: '荆门市',
- 420802: '东宝区',
- 420804: '掇刀区',
- 420821: '京山县',
- 420822: '沙洋县',
- 420881: '钟祥市',
- 420882: '其它区',
- 420900: '孝感市',
- 420902: '孝南区',
- 420921: '孝昌县',
- 420922: '大悟县',
- 420923: '云梦县',
- 420981: '应城市',
- 420982: '安陆市',
- 420984: '汉川市',
- 420985: '其它区',
- 421000: '荆州市',
- 421002: '沙市区',
- 421003: '荆州区',
- 421022: '公安县',
- 421023: '监利县',
- 421024: '江陵县',
- 421081: '石首市',
- 421083: '洪湖市',
- 421087: '松滋市',
- 421088: '其它区',
- 421100: '黄冈市',
- 421102: '黄州区',
- 421121: '团风县',
- 421122: '红安县',
- 421123: '罗田县',
- 421124: '英山县',
- 421125: '浠水县',
- 421126: '蕲春县',
- 421127: '黄梅县',
- 421181: '麻城市',
- 421182: '武穴市',
- 421183: '其它区',
- 421200: '咸宁市',
- 421202: '咸安区',
- 421221: '嘉鱼县',
- 421222: '通城县',
- 421223: '崇阳县',
- 421224: '通山县',
- 421281: '赤壁市',
- 421283: '其它区',
- 421300: '随州市',
- 421302: '曾都区',
- 421321: '随县',
- 421381: '广水市',
- 421382: '其它区',
- 422800: '恩施土家族苗族自治州',
- 422801: '恩施市',
- 422802: '利川市',
- 422822: '建始县',
- 422823: '巴东县',
- 422825: '宣恩县',
- 422826: '咸丰县',
- 422827: '来凤县',
- 422828: '鹤峰县',
- 422829: '其它区',
- 429004: '仙桃市',
- 429005: '潜江市',
- 429006: '天门市',
- 429021: '神农架林区',
- 430000: '湖南省',
- 430100: '长沙市',
- 430102: '芙蓉区',
- 430103: '天心区',
- 430104: '岳麓区',
- 430105: '开福区',
- 430111: '雨花区',
- 430121: '长沙县',
- 430122: '望城区',
- 430124: '宁乡县',
- 430181: '浏阳市',
- 430182: '其它区',
- 430200: '株洲市',
- 430202: '荷塘区',
- 430203: '芦淞区',
- 430204: '石峰区',
- 430211: '天元区',
- 430221: '株洲县',
- 430223: '攸县',
- 430224: '茶陵县',
- 430225: '炎陵县',
- 430281: '醴陵市',
- 430282: '其它区',
- 430300: '湘潭市',
- 430302: '雨湖区',
- 430304: '岳塘区',
- 430321: '湘潭县',
- 430381: '湘乡市',
- 430382: '韶山市',
- 430383: '其它区',
- 430400: '衡阳市',
- 430405: '珠晖区',
- 430406: '雁峰区',
- 430407: '石鼓区',
- 430408: '蒸湘区',
- 430412: '南岳区',
- 430421: '衡阳县',
- 430422: '衡南县',
- 430423: '衡山县',
- 430424: '衡东县',
- 430426: '祁东县',
- 430481: '耒阳市',
- 430482: '常宁市',
- 430483: '其它区',
- 430500: '邵阳市',
- 430502: '双清区',
- 430503: '大祥区',
- 430511: '北塔区',
- 430521: '邵东县',
- 430522: '新邵县',
- 430523: '邵阳县',
- 430524: '隆回县',
- 430525: '洞口县',
- 430527: '绥宁县',
- 430528: '新宁县',
- 430529: '城步苗族自治县',
- 430581: '武冈市',
- 430582: '其它区',
- 430600: '岳阳市',
- 430602: '岳阳楼区',
- 430603: '云溪区',
- 430611: '君山区',
- 430621: '岳阳县',
- 430623: '华容县',
- 430624: '湘阴县',
- 430626: '平江县',
- 430681: '汨罗市',
- 430682: '临湘市',
- 430683: '其它区',
- 430700: '常德市',
- 430702: '武陵区',
- 430703: '鼎城区',
- 430721: '安乡县',
- 430722: '汉寿县',
- 430723: '澧县',
- 430724: '临澧县',
- 430725: '桃源县',
- 430726: '石门县',
- 430781: '津市市',
- 430782: '其它区',
- 430800: '张家界市',
- 430802: '永定区',
- 430811: '武陵源区',
- 430821: '慈利县',
- 430822: '桑植县',
- 430823: '其它区',
- 430900: '益阳市',
- 430902: '资阳区',
- 430903: '赫山区',
- 430921: '南县',
- 430922: '桃江县',
- 430923: '安化县',
- 430981: '沅江市',
- 430982: '其它区',
- 431000: '郴州市',
- 431002: '北湖区',
- 431003: '苏仙区',
- 431021: '桂阳县',
- 431022: '宜章县',
- 431023: '永兴县',
- 431024: '嘉禾县',
- 431025: '临武县',
- 431026: '汝城县',
- 431027: '桂东县',
- 431028: '安仁县',
- 431081: '资兴市',
- 431082: '其它区',
- 431100: '永州市',
- 431102: '零陵区',
- 431103: '冷水滩区',
- 431121: '祁阳县',
- 431122: '东安县',
- 431123: '双牌县',
- 431124: '道县',
- 431125: '江永县',
- 431126: '宁远县',
- 431127: '蓝山县',
- 431128: '新田县',
- 431129: '江华瑶族自治县',
- 431130: '其它区',
- 431200: '怀化市',
- 431202: '鹤城区',
- 431221: '中方县',
- 431222: '沅陵县',
- 431223: '辰溪县',
- 431224: '溆浦县',
- 431225: '会同县',
- 431226: '麻阳苗族自治县',
- 431227: '新晃侗族自治县',
- 431228: '芷江侗族自治县',
- 431229: '靖州苗族侗族自治县',
- 431230: '通道侗族自治县',
- 431281: '洪江市',
- 431282: '其它区',
- 431300: '娄底市',
- 431302: '娄星区',
- 431321: '双峰县',
- 431322: '新化县',
- 431381: '冷水江市',
- 431382: '涟源市',
- 431383: '其它区',
- 433100: '湘西土家族苗族自治州',
- 433101: '吉首市',
- 433122: '泸溪县',
- 433123: '凤凰县',
- 433124: '花垣县',
- 433125: '保靖县',
- 433126: '古丈县',
- 433127: '永顺县',
- 433130: '龙山县',
- 433131: '其它区',
- 440000: '广东省',
- 440100: '广州市',
- 440103: '荔湾区',
- 440104: '越秀区',
- 440105: '海珠区',
- 440106: '天河区',
- 440111: '白云区',
- 440112: '黄埔区',
- 440113: '番禺区',
- 440114: '花都区',
- 440115: '南沙区',
- 440116: '萝岗区',
- 440183: '增城市',
- 440184: '从化市',
- 440189: '其它区',
- 440200: '韶关市',
- 440203: '武江区',
- 440204: '浈江区',
- 440205: '曲江区',
- 440222: '始兴县',
- 440224: '仁化县',
- 440229: '翁源县',
- 440232: '乳源瑶族自治县',
- 440233: '新丰县',
- 440281: '乐昌市',
- 440282: '南雄市',
- 440283: '其它区',
- 440300: '深圳市',
- 440303: '罗湖区',
- 440304: '福田区',
- 440305: '南山区',
- 440306: '宝安区',
- 440307: '龙岗区',
- 440308: '盐田区',
- 440309: '其它区',
- 440320: '光明新区',
- 440321: '坪山新区',
- 440322: '大鹏新区',
- 440323: '龙华新区',
- 440400: '珠海市',
- 440402: '香洲区',
- 440403: '斗门区',
- 440404: '金湾区',
- 440488: '其它区',
- 440500: '汕头市',
- 440507: '龙湖区',
- 440511: '金平区',
- 440512: '濠江区',
- 440513: '潮阳区',
- 440514: '潮南区',
- 440515: '澄海区',
- 440523: '南澳县',
- 440524: '其它区',
- 440600: '佛山市',
- 440604: '禅城区',
- 440605: '南海区',
- 440606: '顺德区',
- 440607: '三水区',
- 440608: '高明区',
- 440609: '其它区',
- 440700: '江门市',
- 440703: '蓬江区',
- 440704: '江海区',
- 440705: '新会区',
- 440781: '台山市',
- 440783: '开平市',
- 440784: '鹤山市',
- 440785: '恩平市',
- 440786: '其它区',
- 440800: '湛江市',
- 440802: '赤坎区',
- 440803: '霞山区',
- 440804: '坡头区',
- 440811: '麻章区',
- 440823: '遂溪县',
- 440825: '徐闻县',
- 440881: '廉江市',
- 440882: '雷州市',
- 440883: '吴川市',
- 440884: '其它区',
- 440900: '茂名市',
- 440902: '茂南区',
- 440903: '茂港区',
- 440923: '电白县',
- 440981: '高州市',
- 440982: '化州市',
- 440983: '信宜市',
- 440984: '其它区',
- 441200: '肇庆市',
- 441202: '端州区',
- 441203: '鼎湖区',
- 441223: '广宁县',
- 441224: '怀集县',
- 441225: '封开县',
- 441226: '德庆县',
- 441283: '高要市',
- 441284: '四会市',
- 441285: '其它区',
- 441300: '惠州市',
- 441302: '惠城区',
- 441303: '惠阳区',
- 441322: '博罗县',
- 441323: '惠东县',
- 441324: '龙门县',
- 441325: '其它区',
- 441400: '梅州市',
- 441402: '梅江区',
- 441421: '梅县',
- 441422: '大埔县',
- 441423: '丰顺县',
- 441424: '五华县',
- 441426: '平远县',
- 441427: '蕉岭县',
- 441481: '兴宁市',
- 441482: '其它区',
- 441500: '汕尾市',
- 441502: '城区',
- 441521: '海丰县',
- 441523: '陆河县',
- 441581: '陆丰市',
- 441582: '其它区',
- 441600: '河源市',
- 441602: '源城区',
- 441621: '紫金县',
- 441622: '龙川县',
- 441623: '连平县',
- 441624: '和平县',
- 441625: '东源县',
- 441626: '其它区',
- 441700: '阳江市',
- 441702: '江城区',
- 441721: '阳西县',
- 441723: '阳东县',
- 441781: '阳春市',
- 441782: '其它区',
- 441800: '清远市',
- 441802: '清城区',
- 441821: '佛冈县',
- 441823: '阳山县',
- 441825: '连山壮族瑶族自治县',
- 441826: '连南瑶族自治县',
- 441827: '清新区',
- 441881: '英德市',
- 441882: '连州市',
- 441883: '其它区',
- 441900: '东莞市',
- 442000: '中山市',
- 442101: '东沙群岛',
- 445100: '潮州市',
- 445102: '湘桥区',
- 445121: '潮安区',
- 445122: '饶平县',
- 445186: '其它区',
- 445200: '揭阳市',
- 445202: '榕城区',
- 445221: '揭东区',
- 445222: '揭西县',
- 445224: '惠来县',
- 445281: '普宁市',
- 445285: '其它区',
- 445300: '云浮市',
- 445302: '云城区',
- 445321: '新兴县',
- 445322: '郁南县',
- 445323: '云安县',
- 445381: '罗定市',
- 445382: '其它区',
- 450000: '广西壮族自治区',
- 450100: '南宁市',
- 450102: '兴宁区',
- 450103: '青秀区',
- 450105: '江南区',
- 450107: '西乡塘区',
- 450108: '良庆区',
- 450109: '邕宁区',
- 450122: '武鸣县',
- 450123: '隆安县',
- 450124: '马山县',
- 450125: '上林县',
- 450126: '宾阳县',
- 450127: '横县',
- 450128: '其它区',
- 450200: '柳州市',
- 450202: '城中区',
- 450203: '鱼峰区',
- 450204: '柳南区',
- 450205: '柳北区',
- 450221: '柳江县',
- 450222: '柳城县',
- 450223: '鹿寨县',
- 450224: '融安县',
- 450225: '融水苗族自治县',
- 450226: '三江侗族自治县',
- 450227: '其它区',
- 450300: '桂林市',
- 450302: '秀峰区',
- 450303: '叠彩区',
- 450304: '象山区',
- 450305: '七星区',
- 450311: '雁山区',
- 450321: '阳朔县',
- 450322: '临桂区',
- 450323: '灵川县',
- 450324: '全州县',
- 450325: '兴安县',
- 450326: '永福县',
- 450327: '灌阳县',
- 450328: '龙胜各族自治县',
- 450329: '资源县',
- 450330: '平乐县',
- 450331: '荔浦县',
- 450332: '恭城瑶族自治县',
- 450333: '其它区',
- 450400: '梧州市',
- 450403: '万秀区',
- 450405: '长洲区',
- 450406: '龙圩区',
- 450421: '苍梧县',
- 450422: '藤县',
- 450423: '蒙山县',
- 450481: '岑溪市',
- 450482: '其它区',
- 450500: '北海市',
- 450502: '海城区',
- 450503: '银海区',
- 450512: '铁山港区',
- 450521: '合浦县',
- 450522: '其它区',
- 450600: '防城港市',
- 450602: '港口区',
- 450603: '防城区',
- 450621: '上思县',
- 450681: '东兴市',
- 450682: '其它区',
- 450700: '钦州市',
- 450702: '钦南区',
- 450703: '钦北区',
- 450721: '灵山县',
- 450722: '浦北县',
- 450723: '其它区',
- 450800: '贵港市',
- 450802: '港北区',
- 450803: '港南区',
- 450804: '覃塘区',
- 450821: '平南县',
- 450881: '桂平市',
- 450882: '其它区',
- 450900: '玉林市',
- 450902: '玉州区',
- 450903: '福绵区',
- 450921: '容县',
- 450922: '陆川县',
- 450923: '博白县',
- 450924: '兴业县',
- 450981: '北流市',
- 450982: '其它区',
- 451000: '百色市',
- 451002: '右江区',
- 451021: '田阳县',
- 451022: '田东县',
- 451023: '平果县',
- 451024: '德保县',
- 451025: '靖西县',
- 451026: '那坡县',
- 451027: '凌云县',
- 451028: '乐业县',
- 451029: '田林县',
- 451030: '西林县',
- 451031: '隆林各族自治县',
- 451032: '其它区',
- 451100: '贺州市',
- 451102: '八步区',
- 451119: '平桂管理区',
- 451121: '昭平县',
- 451122: '钟山县',
- 451123: '富川瑶族自治县',
- 451124: '其它区',
- 451200: '河池市',
- 451202: '金城江区',
- 451221: '南丹县',
- 451222: '天峨县',
- 451223: '凤山县',
- 451224: '东兰县',
- 451225: '罗城仫佬族自治县',
- 451226: '环江毛南族自治县',
- 451227: '巴马瑶族自治县',
- 451228: '都安瑶族自治县',
- 451229: '大化瑶族自治县',
- 451281: '宜州市',
- 451282: '其它区',
- 451300: '来宾市',
- 451302: '兴宾区',
- 451321: '忻城县',
- 451322: '象州县',
- 451323: '武宣县',
- 451324: '金秀瑶族自治县',
- 451381: '合山市',
- 451382: '其它区',
- 451400: '崇左市',
- 451402: '江州区',
- 451421: '扶绥县',
- 451422: '宁明县',
- 451423: '龙州县',
- 451424: '大新县',
- 451425: '天等县',
- 451481: '凭祥市',
- 451482: '其它区',
- 460000: '海南省',
- 460100: '海口市',
- 460105: '秀英区',
- 460106: '龙华区',
- 460107: '琼山区',
- 460108: '美兰区',
- 460109: '其它区',
- 460200: '三亚市',
- 460300: '三沙市',
- 460321: '西沙群岛',
- 460322: '南沙群岛',
- 460323: '中沙群岛的岛礁及其海域',
- 469001: '五指山市',
- 469002: '琼海市',
- 469003: '儋州市',
- 469005: '文昌市',
- 469006: '万宁市',
- 469007: '东方市',
- 469025: '定安县',
- 469026: '屯昌县',
- 469027: '澄迈县',
- 469028: '临高县',
- 469030: '白沙黎族自治县',
- 469031: '昌江黎族自治县',
- 469033: '乐东黎族自治县',
- 469034: '陵水黎族自治县',
- 469035: '保亭黎族苗族自治县',
- 469036: '琼中黎族苗族自治县',
- 471005: '其它区',
- 500000: '重庆',
- 500100: '重庆市',
- 500101: '万州区',
- 500102: '涪陵区',
- 500103: '渝中区',
- 500104: '大渡口区',
- 500105: '江北区',
- 500106: '沙坪坝区',
- 500107: '九龙坡区',
- 500108: '南岸区',
- 500109: '北碚区',
- 500110: '万盛区',
- 500111: '双桥区',
- 500112: '渝北区',
- 500113: '巴南区',
- 500114: '黔江区',
- 500115: '长寿区',
- 500222: '綦江区',
- 500223: '潼南县',
- 500224: '铜梁县',
- 500225: '大足区',
- 500226: '荣昌县',
- 500227: '璧山县',
- 500228: '梁平县',
- 500229: '城口县',
- 500230: '丰都县',
- 500231: '垫江县',
- 500232: '武隆县',
- 500233: '忠县',
- 500234: '开县',
- 500235: '云阳县',
- 500236: '奉节县',
- 500237: '巫山县',
- 500238: '巫溪县',
- 500240: '石柱土家族自治县',
- 500241: '秀山土家族苗族自治县',
- 500242: '酉阳土家族苗族自治县',
- 500243: '彭水苗族土家族自治县',
- 500381: '江津区',
- 500382: '合川区',
- 500383: '永川区',
- 500384: '南川区',
- 500385: '其它区',
- 510000: '四川省',
- 510100: '成都市',
- 510104: '锦江区',
- 510105: '青羊区',
- 510106: '金牛区',
- 510107: '武侯区',
- 510108: '成华区',
- 510112: '龙泉驿区',
- 510113: '青白江区',
- 510114: '新都区',
- 510115: '温江区',
- 510121: '金堂县',
- 510122: '双流县',
- 510124: '郫县',
- 510129: '大邑县',
- 510131: '蒲江县',
- 510132: '新津县',
- 510181: '都江堰市',
- 510182: '彭州市',
- 510183: '邛崃市',
- 510184: '崇州市',
- 510185: '其它区',
- 510300: '自贡市',
- 510302: '自流井区',
- 510303: '贡井区',
- 510304: '大安区',
- 510311: '沿滩区',
- 510321: '荣县',
- 510322: '富顺县',
- 510323: '其它区',
- 510400: '攀枝花市',
- 510402: '东区',
- 510403: '西区',
- 510411: '仁和区',
- 510421: '米易县',
- 510422: '盐边县',
- 510423: '其它区',
- 510500: '泸州市',
- 510502: '江阳区',
- 510503: '纳溪区',
- 510504: '龙马潭区',
- 510521: '泸县',
- 510522: '合江县',
- 510524: '叙永县',
- 510525: '古蔺县',
- 510526: '其它区',
- 510600: '德阳市',
- 510603: '旌阳区',
- 510623: '中江县',
- 510626: '罗江县',
- 510681: '广汉市',
- 510682: '什邡市',
- 510683: '绵竹市',
- 510684: '其它区',
- 510700: '绵阳市',
- 510703: '涪城区',
- 510704: '游仙区',
- 510722: '三台县',
- 510723: '盐亭县',
- 510724: '安县',
- 510725: '梓潼县',
- 510726: '北川羌族自治县',
- 510727: '平武县',
- 510781: '江油市',
- 510782: '其它区',
- 510800: '广元市',
- 510802: '利州区',
- 510811: '昭化区',
- 510812: '朝天区',
- 510821: '旺苍县',
- 510822: '青川县',
- 510823: '剑阁县',
- 510824: '苍溪县',
- 510825: '其它区',
- 510900: '遂宁市',
- 510903: '船山区',
- 510904: '安居区',
- 510921: '蓬溪县',
- 510922: '射洪县',
- 510923: '大英县',
- 510924: '其它区',
- 511000: '内江市',
- 511002: '市中区',
- 511011: '东兴区',
- 511024: '威远县',
- 511025: '资中县',
- 511028: '隆昌县',
- 511029: '其它区',
- 511100: '乐山市',
- 511102: '市中区',
- 511111: '沙湾区',
- 511112: '五通桥区',
- 511113: '金口河区',
- 511123: '犍为县',
- 511124: '井研县',
- 511126: '夹江县',
- 511129: '沐川县',
- 511132: '峨边彝族自治县',
- 511133: '马边彝族自治县',
- 511181: '峨眉山市',
- 511182: '其它区',
- 511300: '南充市',
- 511302: '顺庆区',
- 511303: '高坪区',
- 511304: '嘉陵区',
- 511321: '南部县',
- 511322: '营山县',
- 511323: '蓬安县',
- 511324: '仪陇县',
- 511325: '西充县',
- 511381: '阆中市',
- 511382: '其它区',
- 511400: '眉山市',
- 511402: '东坡区',
- 511421: '仁寿县',
- 511422: '彭山县',
- 511423: '洪雅县',
- 511424: '丹棱县',
- 511425: '青神县',
- 511426: '其它区',
- 511500: '宜宾市',
- 511502: '翠屏区',
- 511521: '宜宾县',
- 511522: '南溪区',
- 511523: '江安县',
- 511524: '长宁县',
- 511525: '高县',
- 511526: '珙县',
- 511527: '筠连县',
- 511528: '兴文县',
- 511529: '屏山县',
- 511530: '其它区',
- 511600: '广安市',
- 511602: '广安区',
- 511603: '前锋区',
- 511621: '岳池县',
- 511622: '武胜县',
- 511623: '邻水县',
- 511681: '华蓥市',
- 511683: '其它区',
- 511700: '达州市',
- 511702: '通川区',
- 511721: '达川区',
- 511722: '宣汉县',
- 511723: '开江县',
- 511724: '大竹县',
- 511725: '渠县',
- 511781: '万源市',
- 511782: '其它区',
- 511800: '雅安市',
- 511802: '雨城区',
- 511821: '名山区',
- 511822: '荥经县',
- 511823: '汉源县',
- 511824: '石棉县',
- 511825: '天全县',
- 511826: '芦山县',
- 511827: '宝兴县',
- 511828: '其它区',
- 511900: '巴中市',
- 511902: '巴州区',
- 511903: '恩阳区',
- 511921: '通江县',
- 511922: '南江县',
- 511923: '平昌县',
- 511924: '其它区',
- 512000: '资阳市',
- 512002: '雁江区',
- 512021: '安岳县',
- 512022: '乐至县',
- 512081: '简阳市',
- 512082: '其它区',
- 513200: '阿坝藏族羌族自治州',
- 513221: '汶川县',
- 513222: '理县',
- 513223: '茂县',
- 513224: '松潘县',
- 513225: '九寨沟县',
- 513226: '金川县',
- 513227: '小金县',
- 513228: '黑水县',
- 513229: '马尔康县',
- 513230: '壤塘县',
- 513231: '阿坝县',
- 513232: '若尔盖县',
- 513233: '红原县',
- 513234: '其它区',
- 513300: '甘孜藏族自治州',
- 513321: '康定县',
- 513322: '泸定县',
- 513323: '丹巴县',
- 513324: '九龙县',
- 513325: '雅江县',
- 513326: '道孚县',
- 513327: '炉霍县',
- 513328: '甘孜县',
- 513329: '新龙县',
- 513330: '德格县',
- 513331: '白玉县',
- 513332: '石渠县',
- 513333: '色达县',
- 513334: '理塘县',
- 513335: '巴塘县',
- 513336: '乡城县',
- 513337: '稻城县',
- 513338: '得荣县',
- 513339: '其它区',
- 513400: '凉山彝族自治州',
- 513401: '西昌市',
- 513422: '木里藏族自治县',
- 513423: '盐源县',
- 513424: '德昌县',
- 513425: '会理县',
- 513426: '会东县',
- 513427: '宁南县',
- 513428: '普格县',
- 513429: '布拖县',
- 513430: '金阳县',
- 513431: '昭觉县',
- 513432: '喜德县',
- 513433: '冕宁县',
- 513434: '越西县',
- 513435: '甘洛县',
- 513436: '美姑县',
- 513437: '雷波县',
- 513438: '其它区',
- 520000: '贵州省',
- 520100: '贵阳市',
- 520102: '南明区',
- 520103: '云岩区',
- 520111: '花溪区',
- 520112: '乌当区',
- 520113: '白云区',
- 520121: '开阳县',
- 520122: '息烽县',
- 520123: '修文县',
- 520151: '观山湖区',
- 520181: '清镇市',
- 520182: '其它区',
- 520200: '六盘水市',
- 520201: '钟山区',
- 520203: '六枝特区',
- 520221: '水城县',
- 520222: '盘县',
- 520223: '其它区',
- 520300: '遵义市',
- 520302: '红花岗区',
- 520303: '汇川区',
- 520321: '遵义县',
- 520322: '桐梓县',
- 520323: '绥阳县',
- 520324: '正安县',
- 520325: '道真仡佬族苗族自治县',
- 520326: '务川仡佬族苗族自治县',
- 520327: '凤冈县',
- 520328: '湄潭县',
- 520329: '余庆县',
- 520330: '习水县',
- 520381: '赤水市',
- 520382: '仁怀市',
- 520383: '其它区',
- 520400: '安顺市',
- 520402: '西秀区',
- 520421: '平坝县',
- 520422: '普定县',
- 520423: '镇宁布依族苗族自治县',
- 520424: '关岭布依族苗族自治县',
- 520425: '紫云苗族布依族自治县',
- 520426: '其它区',
- 522200: '铜仁市',
- 522201: '碧江区',
- 522222: '江口县',
- 522223: '玉屏侗族自治县',
- 522224: '石阡县',
- 522225: '思南县',
- 522226: '印江土家族苗族自治县',
- 522227: '德江县',
- 522228: '沿河土家族自治县',
- 522229: '松桃苗族自治县',
- 522230: '万山区',
- 522231: '其它区',
- 522300: '黔西南布依族苗族自治州',
- 522301: '兴义市',
- 522322: '兴仁县',
- 522323: '普安县',
- 522324: '晴隆县',
- 522325: '贞丰县',
- 522326: '望谟县',
- 522327: '册亨县',
- 522328: '安龙县',
- 522329: '其它区',
- 522400: '毕节市',
- 522401: '七星关区',
- 522422: '大方县',
- 522423: '黔西县',
- 522424: '金沙县',
- 522425: '织金县',
- 522426: '纳雍县',
- 522427: '威宁彝族回族苗族自治县',
- 522428: '赫章县',
- 522429: '其它区',
- 522600: '黔东南苗族侗族自治州',
- 522601: '凯里市',
- 522622: '黄平县',
- 522623: '施秉县',
- 522624: '三穗县',
- 522625: '镇远县',
- 522626: '岑巩县',
- 522627: '天柱县',
- 522628: '锦屏县',
- 522629: '剑河县',
- 522630: '台江县',
- 522631: '黎平县',
- 522632: '榕江县',
- 522633: '从江县',
- 522634: '雷山县',
- 522635: '麻江县',
- 522636: '丹寨县',
- 522637: '其它区',
- 522700: '黔南布依族苗族自治州',
- 522701: '都匀市',
- 522702: '福泉市',
- 522722: '荔波县',
- 522723: '贵定县',
- 522725: '瓮安县',
- 522726: '独山县',
- 522727: '平塘县',
- 522728: '罗甸县',
- 522729: '长顺县',
- 522730: '龙里县',
- 522731: '惠水县',
- 522732: '三都水族自治县',
- 522733: '其它区',
- 530000: '云南省',
- 530100: '昆明市',
- 530102: '五华区',
- 530103: '盘龙区',
- 530111: '官渡区',
- 530112: '西山区',
- 530113: '东川区',
- 530121: '呈贡区',
- 530122: '晋宁县',
- 530124: '富民县',
- 530125: '宜良县',
- 530126: '石林彝族自治县',
- 530127: '嵩明县',
- 530128: '禄劝彝族苗族自治县',
- 530129: '寻甸回族彝族自治县',
- 530181: '安宁市',
- 530182: '其它区',
- 530300: '曲靖市',
- 530302: '麒麟区',
- 530321: '马龙县',
- 530322: '陆良县',
- 530323: '师宗县',
- 530324: '罗平县',
- 530325: '富源县',
- 530326: '会泽县',
- 530328: '沾益县',
- 530381: '宣威市',
- 530382: '其它区',
- 530400: '玉溪市',
- 530402: '红塔区',
- 530421: '江川县',
- 530422: '澄江县',
- 530423: '通海县',
- 530424: '华宁县',
- 530425: '易门县',
- 530426: '峨山彝族自治县',
- 530427: '新平彝族傣族自治县',
- 530428: '元江哈尼族彝族傣族自治县',
- 530429: '其它区',
- 530500: '保山市',
- 530502: '隆阳区',
- 530521: '施甸县',
- 530522: '腾冲县',
- 530523: '龙陵县',
- 530524: '昌宁县',
- 530525: '其它区',
- 530600: '昭通市',
- 530602: '昭阳区',
- 530621: '鲁甸县',
- 530622: '巧家县',
- 530623: '盐津县',
- 530624: '大关县',
- 530625: '永善县',
- 530626: '绥江县',
- 530627: '镇雄县',
- 530628: '彝良县',
- 530629: '威信县',
- 530630: '水富县',
- 530631: '其它区',
- 530700: '丽江市',
- 530702: '古城区',
- 530721: '玉龙纳西族自治县',
- 530722: '永胜县',
- 530723: '华坪县',
- 530724: '宁蒗彝族自治县',
- 530725: '其它区',
- 530800: '普洱市',
- 530802: '思茅区',
- 530821: '宁洱哈尼族彝族自治县',
- 530822: '墨江哈尼族自治县',
- 530823: '景东彝族自治县',
- 530824: '景谷傣族彝族自治县',
- 530825: '镇沅彝族哈尼族拉祜族自治县',
- 530826: '江城哈尼族彝族自治县',
- 530827: '孟连傣族拉祜族佤族自治县',
- 530828: '澜沧拉祜族自治县',
- 530829: '西盟佤族自治县',
- 530830: '其它区',
- 530900: '临沧市',
- 530902: '临翔区',
- 530921: '凤庆县',
- 530922: '云县',
- 530923: '永德县',
- 530924: '镇康县',
- 530925: '双江拉祜族佤族布朗族傣族自治县',
- 530926: '耿马傣族佤族自治县',
- 530927: '沧源佤族自治县',
- 530928: '其它区',
- 532300: '楚雄彝族自治州',
- 532301: '楚雄市',
- 532322: '双柏县',
- 532323: '牟定县',
- 532324: '南华县',
- 532325: '姚安县',
- 532326: '大姚县',
- 532327: '永仁县',
- 532328: '元谋县',
- 532329: '武定县',
- 532331: '禄丰县',
- 532332: '其它区',
- 532500: '红河哈尼族彝族自治州',
- 532501: '个旧市',
- 532502: '开远市',
- 532522: '蒙自市',
- 532523: '屏边苗族自治县',
- 532524: '建水县',
- 532525: '石屏县',
- 532526: '弥勒市',
- 532527: '泸西县',
- 532528: '元阳县',
- 532529: '红河县',
- 532530: '金平苗族瑶族傣族自治县',
- 532531: '绿春县',
- 532532: '河口瑶族自治县',
- 532533: '其它区',
- 532600: '文山壮族苗族自治州',
- 532621: '文山市',
- 532622: '砚山县',
- 532623: '西畴县',
- 532624: '麻栗坡县',
- 532625: '马关县',
- 532626: '丘北县',
- 532627: '广南县',
- 532628: '富宁县',
- 532629: '其它区',
- 532800: '西双版纳傣族自治州',
- 532801: '景洪市',
- 532822: '勐海县',
- 532823: '勐腊县',
- 532824: '其它区',
- 532900: '大理白族自治州',
- 532901: '大理市',
- 532922: '漾濞彝族自治县',
- 532923: '祥云县',
- 532924: '宾川县',
- 532925: '弥渡县',
- 532926: '南涧彝族自治县',
- 532927: '巍山彝族回族自治县',
- 532928: '永平县',
- 532929: '云龙县',
- 532930: '洱源县',
- 532931: '剑川县',
- 532932: '鹤庆县',
- 532933: '其它区',
- 533100: '德宏傣族景颇族自治州',
- 533102: '瑞丽市',
- 533103: '芒市',
- 533122: '梁河县',
- 533123: '盈江县',
- 533124: '陇川县',
- 533125: '其它区',
- 533300: '怒江傈僳族自治州',
- 533321: '泸水县',
- 533323: '福贡县',
- 533324: '贡山独龙族怒族自治县',
- 533325: '兰坪白族普米族自治县',
- 533326: '其它区',
- 533400: '迪庆藏族自治州',
- 533421: '香格里拉县',
- 533422: '德钦县',
- 533423: '维西傈僳族自治县',
- 533424: '其它区',
- 540000: '西藏自治区',
- 540100: '拉萨市',
- 540102: '城关区',
- 540121: '林周县',
- 540122: '当雄县',
- 540123: '尼木县',
- 540124: '曲水县',
- 540125: '堆龙德庆县',
- 540126: '达孜县',
- 540127: '墨竹工卡县',
- 540128: '其它区',
- 542100: '昌都地区',
- 542121: '昌都县',
- 542122: '江达县',
- 542123: '贡觉县',
- 542124: '类乌齐县',
- 542125: '丁青县',
- 542126: '察雅县',
- 542127: '八宿县',
- 542128: '左贡县',
- 542129: '芒康县',
- 542132: '洛隆县',
- 542133: '边坝县',
- 542134: '其它区',
- 542200: '山南地区',
- 542221: '乃东县',
- 542222: '扎囊县',
- 542223: '贡嘎县',
- 542224: '桑日县',
- 542225: '琼结县',
- 542226: '曲松县',
- 542227: '措美县',
- 542228: '洛扎县',
- 542229: '加查县',
- 542231: '隆子县',
- 542232: '错那县',
- 542233: '浪卡子县',
- 542234: '其它区',
- 542300: '日喀则地区',
- 542301: '日喀则市',
- 542322: '南木林县',
- 542323: '江孜县',
- 542324: '定日县',
- 542325: '萨迦县',
- 542326: '拉孜县',
- 542327: '昂仁县',
- 542328: '谢通门县',
- 542329: '白朗县',
- 542330: '仁布县',
- 542331: '康马县',
- 542332: '定结县',
- 542333: '仲巴县',
- 542334: '亚东县',
- 542335: '吉隆县',
- 542336: '聂拉木县',
- 542337: '萨嘎县',
- 542338: '岗巴县',
- 542339: '其它区',
- 542400: '那曲地区',
- 542421: '那曲县',
- 542422: '嘉黎县',
- 542423: '比如县',
- 542424: '聂荣县',
- 542425: '安多县',
- 542426: '申扎县',
- 542427: '索县',
- 542428: '班戈县',
- 542429: '巴青县',
- 542430: '尼玛县',
- 542431: '其它区',
- 542432: '双湖县',
- 542500: '阿里地区',
- 542521: '普兰县',
- 542522: '札达县',
- 542523: '噶尔县',
- 542524: '日土县',
- 542525: '革吉县',
- 542526: '改则县',
- 542527: '措勤县',
- 542528: '其它区',
- 542600: '林芝地区',
- 542621: '林芝县',
- 542622: '工布江达县',
- 542623: '米林县',
- 542624: '墨脱县',
- 542625: '波密县',
- 542626: '察隅县',
- 542627: '朗县',
- 542628: '其它区',
- 610000: '陕西省',
- 610100: '西安市',
- 610102: '新城区',
- 610103: '碑林区',
- 610104: '莲湖区',
- 610111: '灞桥区',
- 610112: '未央区',
- 610113: '雁塔区',
- 610114: '阎良区',
- 610115: '临潼区',
- 610116: '长安区',
- 610122: '蓝田县',
- 610124: '周至县',
- 610125: '户县',
- 610126: '高陵县',
- 610127: '其它区',
- 610200: '铜川市',
- 610202: '王益区',
- 610203: '印台区',
- 610204: '耀州区',
- 610222: '宜君县',
- 610223: '其它区',
- 610300: '宝鸡市',
- 610302: '渭滨区',
- 610303: '金台区',
- 610304: '陈仓区',
- 610322: '凤翔县',
- 610323: '岐山县',
- 610324: '扶风县',
- 610326: '眉县',
- 610327: '陇县',
- 610328: '千阳县',
- 610329: '麟游县',
- 610330: '凤县',
- 610331: '太白县',
- 610332: '其它区',
- 610400: '咸阳市',
- 610402: '秦都区',
- 610403: '杨陵区',
- 610404: '渭城区',
- 610422: '三原县',
- 610423: '泾阳县',
- 610424: '乾县',
- 610425: '礼泉县',
- 610426: '永寿县',
- 610427: '彬县',
- 610428: '长武县',
- 610429: '旬邑县',
- 610430: '淳化县',
- 610431: '武功县',
- 610481: '兴平市',
- 610482: '其它区',
- 610500: '渭南市',
- 610502: '临渭区',
- 610521: '华县',
- 610522: '潼关县',
- 610523: '大荔县',
- 610524: '合阳县',
- 610525: '澄城县',
- 610526: '蒲城县',
- 610527: '白水县',
- 610528: '富平县',
- 610581: '韩城市',
- 610582: '华阴市',
- 610583: '其它区',
- 610600: '延安市',
- 610602: '宝塔区',
- 610621: '延长县',
- 610622: '延川县',
- 610623: '子长县',
- 610624: '安塞县',
- 610625: '志丹县',
- 610626: '吴起县',
- 610627: '甘泉县',
- 610628: '富县',
- 610629: '洛川县',
- 610630: '宜川县',
- 610631: '黄龙县',
- 610632: '黄陵县',
- 610633: '其它区',
- 610700: '汉中市',
- 610702: '汉台区',
- 610721: '南郑县',
- 610722: '城固县',
- 610723: '洋县',
- 610724: '西乡县',
- 610725: '勉县',
- 610726: '宁强县',
- 610727: '略阳县',
- 610728: '镇巴县',
- 610729: '留坝县',
- 610730: '佛坪县',
- 610731: '其它区',
- 610800: '榆林市',
- 610802: '榆阳区',
- 610821: '神木县',
- 610822: '府谷县',
- 610823: '横山县',
- 610824: '靖边县',
- 610825: '定边县',
- 610826: '绥德县',
- 610827: '米脂县',
- 610828: '佳县',
- 610829: '吴堡县',
- 610830: '清涧县',
- 610831: '子洲县',
- 610832: '其它区',
- 610900: '安康市',
- 610902: '汉滨区',
- 610921: '汉阴县',
- 610922: '石泉县',
- 610923: '宁陕县',
- 610924: '紫阳县',
- 610925: '岚皋县',
- 610926: '平利县',
- 610927: '镇坪县',
- 610928: '旬阳县',
- 610929: '白河县',
- 610930: '其它区',
- 611000: '商洛市',
- 611002: '商州区',
- 611021: '洛南县',
- 611022: '丹凤县',
- 611023: '商南县',
- 611024: '山阳县',
- 611025: '镇安县',
- 611026: '柞水县',
- 611027: '其它区',
- 620000: '甘肃省',
- 620100: '兰州市',
- 620102: '城关区',
- 620103: '七里河区',
- 620104: '西固区',
- 620105: '安宁区',
- 620111: '红古区',
- 620121: '永登县',
- 620122: '皋兰县',
- 620123: '榆中县',
- 620124: '其它区',
- 620200: '嘉峪关市',
- 620300: '金昌市',
- 620302: '金川区',
- 620321: '永昌县',
- 620322: '其它区',
- 620400: '白银市',
- 620402: '白银区',
- 620403: '平川区',
- 620421: '靖远县',
- 620422: '会宁县',
- 620423: '景泰县',
- 620424: '其它区',
- 620500: '天水市',
- 620502: '秦州区',
- 620503: '麦积区',
- 620521: '清水县',
- 620522: '秦安县',
- 620523: '甘谷县',
- 620524: '武山县',
- 620525: '张家川回族自治县',
- 620526: '其它区',
- 620600: '武威市',
- 620602: '凉州区',
- 620621: '民勤县',
- 620622: '古浪县',
- 620623: '天祝藏族自治县',
- 620624: '其它区',
- 620700: '张掖市',
- 620702: '甘州区',
- 620721: '肃南裕固族自治县',
- 620722: '民乐县',
- 620723: '临泽县',
- 620724: '高台县',
- 620725: '山丹县',
- 620726: '其它区',
- 620800: '平凉市',
- 620802: '崆峒区',
- 620821: '泾川县',
- 620822: '灵台县',
- 620823: '崇信县',
- 620824: '华亭县',
- 620825: '庄浪县',
- 620826: '静宁县',
- 620827: '其它区',
- 620900: '酒泉市',
- 620902: '肃州区',
- 620921: '金塔县',
- 620922: '瓜州县',
- 620923: '肃北蒙古族自治县',
- 620924: '阿克塞哈萨克族自治县',
- 620981: '玉门市',
- 620982: '敦煌市',
- 620983: '其它区',
- 621000: '庆阳市',
- 621002: '西峰区',
- 621021: '庆城县',
- 621022: '环县',
- 621023: '华池县',
- 621024: '合水县',
- 621025: '正宁县',
- 621026: '宁县',
- 621027: '镇原县',
- 621028: '其它区',
- 621100: '定西市',
- 621102: '安定区',
- 621121: '通渭县',
- 621122: '陇西县',
- 621123: '渭源县',
- 621124: '临洮县',
- 621125: '漳县',
- 621126: '岷县',
- 621127: '其它区',
- 621200: '陇南市',
- 621202: '武都区',
- 621221: '成县',
- 621222: '文县',
- 621223: '宕昌县',
- 621224: '康县',
- 621225: '西和县',
- 621226: '礼县',
- 621227: '徽县',
- 621228: '两当县',
- 621229: '其它区',
- 622900: '临夏回族自治州',
- 622901: '临夏市',
- 622921: '临夏县',
- 622922: '康乐县',
- 622923: '永靖县',
- 622924: '广河县',
- 622925: '和政县',
- 622926: '东乡族自治县',
- 622927: '积石山保安族东乡族撒拉族自治县',
- 622928: '其它区',
- 623000: '甘南藏族自治州',
- 623001: '合作市',
- 623021: '临潭县',
- 623022: '卓尼县',
- 623023: '舟曲县',
- 623024: '迭部县',
- 623025: '玛曲县',
- 623026: '碌曲县',
- 623027: '夏河县',
- 623028: '其它区',
- 630000: '青海省',
- 630100: '西宁市',
- 630102: '城东区',
- 630103: '城中区',
- 630104: '城西区',
- 630105: '城北区',
- 630121: '大通回族土族自治县',
- 630122: '湟中县',
- 630123: '湟源县',
- 630124: '其它区',
- 632100: '海东市',
- 632121: '平安县',
- 632122: '民和回族土族自治县',
- 632123: '乐都区',
- 632126: '互助土族自治县',
- 632127: '化隆回族自治县',
- 632128: '循化撒拉族自治县',
- 632129: '其它区',
- 632200: '海北藏族自治州',
- 632221: '门源回族自治县',
- 632222: '祁连县',
- 632223: '海晏县',
- 632224: '刚察县',
- 632225: '其它区',
- 632300: '黄南藏族自治州',
- 632321: '同仁县',
- 632322: '尖扎县',
- 632323: '泽库县',
- 632324: '河南蒙古族自治县',
- 632325: '其它区',
- 632500: '海南藏族自治州',
- 632521: '共和县',
- 632522: '同德县',
- 632523: '贵德县',
- 632524: '兴海县',
- 632525: '贵南县',
- 632526: '其它区',
- 632600: '果洛藏族自治州',
- 632621: '玛沁县',
- 632622: '班玛县',
- 632623: '甘德县',
- 632624: '达日县',
- 632625: '久治县',
- 632626: '玛多县',
- 632627: '其它区',
- 632700: '玉树藏族自治州',
- 632721: '玉树市',
- 632722: '杂多县',
- 632723: '称多县',
- 632724: '治多县',
- 632725: '囊谦县',
- 632726: '曲麻莱县',
- 632727: '其它区',
- 632800: '海西蒙古族藏族自治州',
- 632801: '格尔木市',
- 632802: '德令哈市',
- 632821: '乌兰县',
- 632822: '都兰县',
- 632823: '天峻县',
- 632824: '其它区',
- 640000: '宁夏回族自治区',
- 640100: '银川市',
- 640104: '兴庆区',
- 640105: '西夏区',
- 640106: '金凤区',
- 640121: '永宁县',
- 640122: '贺兰县',
- 640181: '灵武市',
- 640182: '其它区',
- 640200: '石嘴山市',
- 640202: '大武口区',
- 640205: '惠农区',
- 640221: '平罗县',
- 640222: '其它区',
- 640300: '吴忠市',
- 640302: '利通区',
- 640303: '红寺堡区',
- 640323: '盐池县',
- 640324: '同心县',
- 640381: '青铜峡市',
- 640382: '其它区',
- 640400: '固原市',
- 640402: '原州区',
- 640422: '西吉县',
- 640423: '隆德县',
- 640424: '泾源县',
- 640425: '彭阳县',
- 640426: '其它区',
- 640500: '中卫市',
- 640502: '沙坡头区',
- 640521: '中宁县',
- 640522: '海原县',
- 640523: '其它区',
- 650000: '新疆维吾尔自治区',
- 650100: '乌鲁木齐市',
- 650102: '天山区',
- 650103: '沙依巴克区',
- 650104: '新市区',
- 650105: '水磨沟区',
- 650106: '头屯河区',
- 650107: '达坂城区',
- 650109: '米东区',
- 650121: '乌鲁木齐县',
- 650122: '其它区',
- 650200: '克拉玛依市',
- 650202: '独山子区',
- 650203: '克拉玛依区',
- 650204: '白碱滩区',
- 650205: '乌尔禾区',
- 650206: '其它区',
- 652100: '吐鲁番地区',
- 652101: '吐鲁番市',
- 652122: '鄯善县',
- 652123: '托克逊县',
- 652124: '其它区',
- 652200: '哈密地区',
- 652201: '哈密市',
- 652222: '巴里坤哈萨克自治县',
- 652223: '伊吾县',
- 652224: '其它区',
- 652300: '昌吉回族自治州',
- 652301: '昌吉市',
- 652302: '阜康市',
- 652323: '呼图壁县',
- 652324: '玛纳斯县',
- 652325: '奇台县',
- 652327: '吉木萨尔县',
- 652328: '木垒哈萨克自治县',
- 652329: '其它区',
- 652700: '博尔塔拉蒙古自治州',
- 652701: '博乐市',
- 652702: '阿拉山口市',
- 652722: '精河县',
- 652723: '温泉县',
- 652724: '其它区',
- 652800: '巴音郭楞蒙古自治州',
- 652801: '库尔勒市',
- 652822: '轮台县',
- 652823: '尉犁县',
- 652824: '若羌县',
- 652825: '且末县',
- 652826: '焉耆回族自治县',
- 652827: '和静县',
- 652828: '和硕县',
- 652829: '博湖县',
- 652830: '其它区',
- 652900: '阿克苏地区',
- 652901: '阿克苏市',
- 652922: '温宿县',
- 652923: '库车县',
- 652924: '沙雅县',
- 652925: '新和县',
- 652926: '拜城县',
- 652927: '乌什县',
- 652928: '阿瓦提县',
- 652929: '柯坪县',
- 652930: '其它区',
- 653000: '克孜勒苏柯尔克孜自治州',
- 653001: '阿图什市',
- 653022: '阿克陶县',
- 653023: '阿合奇县',
- 653024: '乌恰县',
- 653025: '其它区',
- 653100: '喀什地区',
- 653101: '喀什市',
- 653121: '疏附县',
- 653122: '疏勒县',
- 653123: '英吉沙县',
- 653124: '泽普县',
- 653125: '莎车县',
- 653126: '叶城县',
- 653127: '麦盖提县',
- 653128: '岳普湖县',
- 653129: '伽师县',
- 653130: '巴楚县',
- 653131: '塔什库尔干塔吉克自治县',
- 653132: '其它区',
- 653200: '和田地区',
- 653201: '和田市',
- 653221: '和田县',
- 653222: '墨玉县',
- 653223: '皮山县',
- 653224: '洛浦县',
- 653225: '策勒县',
- 653226: '于田县',
- 653227: '民丰县',
- 653228: '其它区',
- 654000: '伊犁哈萨克自治州',
- 654002: '伊宁市',
- 654003: '奎屯市',
- 654021: '伊宁县',
- 654022: '察布查尔锡伯自治县',
- 654023: '霍城县',
- 654024: '巩留县',
- 654025: '新源县',
- 654026: '昭苏县',
- 654027: '特克斯县',
- 654028: '尼勒克县',
- 654029: '其它区',
- 654200: '塔城地区',
- 654201: '塔城市',
- 654202: '乌苏市',
- 654221: '额敏县',
- 654223: '沙湾县',
- 654224: '托里县',
- 654225: '裕民县',
- 654226: '和布克赛尔蒙古自治县',
- 654227: '其它区',
- 654300: '阿勒泰地区',
- 654301: '阿勒泰市',
- 654321: '布尔津县',
- 654322: '富蕴县',
- 654323: '福海县',
- 654324: '哈巴河县',
- 654325: '青河县',
- 654326: '吉木乃县',
- 654327: '其它区',
- 659001: '石河子市',
- 659002: '阿拉尔市',
- 659003: '图木舒克市',
- 659004: '五家渠市',
- 710000: '台湾',
- 710100: '台北市',
- 710101: '中正区',
- 710102: '大同区',
- 710103: '中山区',
- 710104: '松山区',
- 710105: '大安区',
- 710106: '万华区',
- 710107: '信义区',
- 710108: '士林区',
- 710109: '北投区',
- 710110: '内湖区',
- 710111: '南港区',
- 710112: '文山区',
- 710113: '其它区',
- 710200: '高雄市',
- 710201: '新兴区',
- 710202: '前金区',
- 710203: '芩雅区',
- 710204: '盐埕区',
- 710205: '鼓山区',
- 710206: '旗津区',
- 710207: '前镇区',
- 710208: '三民区',
- 710209: '左营区',
- 710210: '楠梓区',
- 710211: '小港区',
- 710212: '其它区',
- 710241: '苓雅区',
- 710242: '仁武区',
- 710243: '大社区',
- 710244: '冈山区',
- 710245: '路竹区',
- 710246: '阿莲区',
- 710247: '田寮区',
- 710248: '燕巢区',
- 710249: '桥头区',
- 710250: '梓官区',
- 710251: '弥陀区',
- 710252: '永安区',
- 710253: '湖内区',
- 710254: '凤山区',
- 710255: '大寮区',
- 710256: '林园区',
- 710257: '鸟松区',
- 710258: '大树区',
- 710259: '旗山区',
- 710260: '美浓区',
- 710261: '六龟区',
- 710262: '内门区',
- 710263: '杉林区',
- 710264: '甲仙区',
- 710265: '桃源区',
- 710266: '那玛夏区',
- 710267: '茂林区',
- 710268: '茄萣区',
- 710300: '台南市',
- 710301: '中西区',
- 710302: '东区',
- 710303: '南区',
- 710304: '北区',
- 710305: '安平区',
- 710306: '安南区',
- 710307: '其它区',
- 710339: '永康区',
- 710340: '归仁区',
- 710341: '新化区',
- 710342: '左镇区',
- 710343: '玉井区',
- 710344: '楠西区',
- 710345: '南化区',
- 710346: '仁德区',
- 710347: '关庙区',
- 710348: '龙崎区',
- 710349: '官田区',
- 710350: '麻豆区',
- 710351: '佳里区',
- 710352: '西港区',
- 710353: '七股区',
- 710354: '将军区',
- 710355: '学甲区',
- 710356: '北门区',
- 710357: '新营区',
- 710358: '后壁区',
- 710359: '白河区',
- 710360: '东山区',
- 710361: '六甲区',
- 710362: '下营区',
- 710363: '柳营区',
- 710364: '盐水区',
- 710365: '善化区',
- 710366: '大内区',
- 710367: '山上区',
- 710368: '新市区',
- 710369: '安定区',
- 710400: '台中市',
- 710401: '中区',
- 710402: '东区',
- 710403: '南区',
- 710404: '西区',
- 710405: '北区',
- 710406: '北屯区',
- 710407: '西屯区',
- 710408: '南屯区',
- 710409: '其它区',
- 710431: '太平区',
- 710432: '大里区',
- 710433: '雾峰区',
- 710434: '乌日区',
- 710435: '丰原区',
- 710436: '后里区',
- 710437: '石冈区',
- 710438: '东势区',
- 710439: '和平区',
- 710440: '新社区',
- 710441: '潭子区',
- 710442: '大雅区',
- 710443: '神冈区',
- 710444: '大肚区',
- 710445: '沙鹿区',
- 710446: '龙井区',
- 710447: '梧栖区',
- 710448: '清水区',
- 710449: '大甲区',
- 710450: '外埔区',
- 710451: '大安区',
- 710500: '金门县',
- 710507: '金沙镇',
- 710508: '金湖镇',
- 710509: '金宁乡',
- 710510: '金城镇',
- 710511: '烈屿乡',
- 710512: '乌坵乡',
- 710600: '南投县',
- 710614: '南投市',
- 710615: '中寮乡',
- 710616: '草屯镇',
- 710617: '国姓乡',
- 710618: '埔里镇',
- 710619: '仁爱乡',
- 710620: '名间乡',
- 710621: '集集镇',
- 710622: '水里乡',
- 710623: '鱼池乡',
- 710624: '信义乡',
- 710625: '竹山镇',
- 710626: '鹿谷乡',
- 710700: '基隆市',
- 710701: '仁爱区',
- 710702: '信义区',
- 710703: '中正区',
- 710704: '中山区',
- 710705: '安乐区',
- 710706: '暖暖区',
- 710707: '七堵区',
- 710708: '其它区',
- 710800: '新竹市',
- 710801: '东区',
- 710802: '北区',
- 710803: '香山区',
- 710804: '其它区',
- 710900: '嘉义市',
- 710901: '东区',
- 710902: '西区',
- 710903: '其它区',
- 711100: '新北市',
- 711130: '万里区',
- 711131: '金山区',
- 711132: '板桥区',
- 711133: '汐止区',
- 711134: '深坑区',
- 711135: '石碇区',
- 711136: '瑞芳区',
- 711137: '平溪区',
- 711138: '双溪区',
- 711139: '贡寮区',
- 711140: '新店区',
- 711141: '坪林区',
- 711142: '乌来区',
- 711143: '永和区',
- 711144: '中和区',
- 711145: '土城区',
- 711146: '三峡区',
- 711147: '树林区',
- 711148: '莺歌区',
- 711149: '三重区',
- 711150: '新庄区',
- 711151: '泰山区',
- 711152: '林口区',
- 711153: '芦洲区',
- 711154: '五股区',
- 711155: '八里区',
- 711156: '淡水区',
- 711157: '三芝区',
- 711158: '石门区',
- 711200: '宜兰县',
- 711214: '宜兰市',
- 711215: '头城镇',
- 711216: '礁溪乡',
- 711217: '壮围乡',
- 711218: '员山乡',
- 711219: '罗东镇',
- 711220: '三星乡',
- 711221: '大同乡',
- 711222: '五结乡',
- 711223: '冬山乡',
- 711224: '苏澳镇',
- 711225: '南澳乡',
- 711226: '钓鱼台',
- 711300: '新竹县',
- 711314: '竹北市',
- 711315: '湖口乡',
- 711316: '新丰乡',
- 711317: '新埔镇',
- 711318: '关西镇',
- 711319: '芎林乡',
- 711320: '宝山乡',
- 711321: '竹东镇',
- 711322: '五峰乡',
- 711323: '横山乡',
- 711324: '尖石乡',
- 711325: '北埔乡',
- 711326: '峨眉乡',
- 711400: '桃园县',
- 711414: '中坜市',
- 711415: '平镇市',
- 711416: '龙潭乡',
- 711417: '杨梅市',
- 711418: '新屋乡',
- 711419: '观音乡',
- 711420: '桃园市',
- 711421: '龟山乡',
- 711422: '八德市',
- 711423: '大溪镇',
- 711424: '复兴乡',
- 711425: '大园乡',
- 711426: '芦竹乡',
- 711500: '苗栗县',
- 711519: '竹南镇',
- 711520: '头份镇',
- 711521: '三湾乡',
- 711522: '南庄乡',
- 711523: '狮潭乡',
- 711524: '后龙镇',
- 711525: '通霄镇',
- 711526: '苑里镇',
- 711527: '苗栗市',
- 711528: '造桥乡',
- 711529: '头屋乡',
- 711530: '公馆乡',
- 711531: '大湖乡',
- 711532: '泰安乡',
- 711533: '铜锣乡',
- 711534: '三义乡',
- 711535: '西湖乡',
- 711536: '卓兰镇',
- 711700: '彰化县',
- 711727: '彰化市',
- 711728: '芬园乡',
- 711729: '花坛乡',
- 711730: '秀水乡',
- 711731: '鹿港镇',
- 711732: '福兴乡',
- 711733: '线西乡',
- 711734: '和美镇',
- 711735: '伸港乡',
- 711736: '员林镇',
- 711737: '社头乡',
- 711738: '永靖乡',
- 711739: '埔心乡',
- 711740: '溪湖镇',
- 711741: '大村乡',
- 711742: '埔盐乡',
- 711743: '田中镇',
- 711744: '北斗镇',
- 711745: '田尾乡',
- 711746: '埤头乡',
- 711747: '溪州乡',
- 711748: '竹塘乡',
- 711749: '二林镇',
- 711750: '大城乡',
- 711751: '芳苑乡',
- 711752: '二水乡',
- 711900: '嘉义县',
- 711919: '番路乡',
- 711920: '梅山乡',
- 711921: '竹崎乡',
- 711922: '阿里山乡',
- 711923: '中埔乡',
- 711924: '大埔乡',
- 711925: '水上乡',
- 711926: '鹿草乡',
- 711927: '太保市',
- 711928: '朴子市',
- 711929: '东石乡',
- 711930: '六脚乡',
- 711931: '新港乡',
- 711932: '民雄乡',
- 711933: '大林镇',
- 711934: '溪口乡',
- 711935: '义竹乡',
- 711936: '布袋镇',
- 712100: '云林县',
- 712121: '斗南镇',
- 712122: '大埤乡',
- 712123: '虎尾镇',
- 712124: '土库镇',
- 712125: '褒忠乡',
- 712126: '东势乡',
- 712127: '台西乡',
- 712128: '仑背乡',
- 712129: '麦寮乡',
- 712130: '斗六市',
- 712131: '林内乡',
- 712132: '古坑乡',
- 712133: '莿桐乡',
- 712134: '西螺镇',
- 712135: '二仑乡',
- 712136: '北港镇',
- 712137: '水林乡',
- 712138: '口湖乡',
- 712139: '四湖乡',
- 712140: '元长乡',
- 712400: '屏东县',
- 712434: '屏东市',
- 712435: '三地门乡',
- 712436: '雾台乡',
- 712437: '玛家乡',
- 712438: '九如乡',
- 712439: '里港乡',
- 712440: '高树乡',
- 712441: '盐埔乡',
- 712442: '长治乡',
- 712443: '麟洛乡',
- 712444: '竹田乡',
- 712445: '内埔乡',
- 712446: '万丹乡',
- 712447: '潮州镇',
- 712448: '泰武乡',
- 712449: '来义乡',
- 712450: '万峦乡',
- 712451: '崁顶乡',
- 712452: '新埤乡',
- 712453: '南州乡',
- 712454: '林边乡',
- 712455: '东港镇',
- 712456: '琉球乡',
- 712457: '佳冬乡',
- 712458: '新园乡',
- 712459: '枋寮乡',
- 712460: '枋山乡',
- 712461: '春日乡',
- 712462: '狮子乡',
- 712463: '车城乡',
- 712464: '牡丹乡',
- 712465: '恒春镇',
- 712466: '满州乡',
- 712500: '台东县',
- 712517: '台东市',
- 712518: '绿岛乡',
- 712519: '兰屿乡',
- 712520: '延平乡',
- 712521: '卑南乡',
- 712522: '鹿野乡',
- 712523: '关山镇',
- 712524: '海端乡',
- 712525: '池上乡',
- 712526: '东河乡',
- 712527: '成功镇',
- 712528: '长滨乡',
- 712529: '金峰乡',
- 712530: '大武乡',
- 712531: '达仁乡',
- 712532: '太麻里乡',
- 712600: '花莲县',
- 712615: '花莲市',
- 712616: '新城乡',
- 712617: '太鲁阁',
- 712618: '秀林乡',
- 712619: '吉安乡',
- 712620: '寿丰乡',
- 712621: '凤林镇',
- 712622: '光复乡',
- 712623: '丰滨乡',
- 712624: '瑞穗乡',
- 712625: '万荣乡',
- 712626: '玉里镇',
- 712627: '卓溪乡',
- 712628: '富里乡',
- 712700: '澎湖县',
- 712707: '马公市',
- 712708: '西屿乡',
- 712709: '望安乡',
- 712710: '七美乡',
- 712711: '白沙乡',
- 712712: '湖西乡',
- 712800: '连江县',
- 712805: '南竿乡',
- 712806: '北竿乡',
- 712807: '莒光乡',
- 712808: '东引乡',
- 810000: '香港特别行政区',
- 810100: '香港岛',
- 810101: '中西区',
- 810102: '湾仔',
- 810103: '东区',
- 810104: '南区',
- 810200: '九龙',
- 810201: '九龙城区',
- 810202: '油尖旺区',
- 810203: '深水埗区',
- 810204: '黄大仙区',
- 810205: '观塘区',
- 810300: '新界',
- 810301: '北区',
- 810302: '大埔区',
- 810303: '沙田区',
- 810304: '西贡区',
- 810305: '元朗区',
- 810306: '屯门区',
- 810307: '荃湾区',
- 810308: '葵青区',
- 810309: '离岛区',
- 820000: '澳门特别行政区',
- 820100: '澳门半岛',
- 820200: '离岛',
- 990000: '海外',
- 990100: '海外',
-}
-
-const tree = list => {
- let hashTable = Object.create(null)
- list.forEach(aData => (hashTable[aData.id] = { ...aData, children: [] }))
- let dataTree = []
- list.forEach(aData => {
- if (aData.pid) {
- if (hashTable[aData.pid])
- hashTable[aData.pid].children.push(hashTable[aData.id])
- } else dataTree.push(hashTable[aData.id])
- })
- return dataTree
-}
-
-let DICT_FIXED = (function() {
- let fixed = []
- for (let id in DICT) {
- if ({}.hasOwnProperty.call(DICT, id)) {
- let pid
- const tmpObj = { id, value: DICT[id], label: DICT[id] }
- if (id.slice(2, 6) !== '0000') {
- pid =
- id.slice(4, 6) === '00'
- ? `${id.slice(0, 2)}0000`
- : `${id.slice(0, 4)}00`
- }
- if (pid) tmpObj.pid = pid
- fixed.push(tmpObj)
- }
- }
- return tree(fixed)
-})()
-
-export default DICT_FIXED
diff --git a/src/utils/config.js b/src/utils/config.js
deleted file mode 100644
index f2b6acd..0000000
--- a/src/utils/config.js
+++ /dev/null
@@ -1,39 +0,0 @@
-module.exports = {
- siteName: 'AntD Admin',
- copyright: 'Ant Design Admin ©2020 zuiidea',
- logoPath: '/logo.svg',
- apiPrefix: '/api/v1',
- fixedHeader: true, // sticky primary layout header
-
- /* Layout configuration, specify which layout to use for route. */
- layouts: [
- {
- name: 'primary',
- include: [/.*/],
- exclude: [/(\/(en|zh))*\/login/],
- },
- ],
-
- /* I18n configuration, `languages` and `defaultLanguage` are required currently. */
- i18n: {
- /* Countrys flags: https://www.flaticon.com/packs/countrys-flags */
- languages: [
- {
- key: 'pt-br',
- title: 'Português',
- flag: '/portugal.svg',
- },
- {
- key: 'en',
- title: 'English',
- flag: '/america.svg',
- },
- {
- key: 'zh',
- title: '中文',
- flag: '/china.svg',
- },
- ],
- defaultLanguage: 'en',
- },
-}
diff --git a/src/utils/config.ts b/src/utils/config.ts
new file mode 100644
index 0000000..70c944a
--- /dev/null
+++ b/src/utils/config.ts
@@ -0,0 +1,11 @@
+/**
+ * 全局配置
+ * 说明给 LLM:用于生成请求时的默认 API 前缀和站点元信息。
+ * - `apiPrefix`:API 路由前缀,供 `src/utils/request.ts` 使用。
+ */
+export default {
+ siteName: 'Antd Admin',
+ copyright: 'Ant Design Admin ©2026 zuiidea',
+ logoPath: '/logo.svg',
+ apiPrefix: '/api/v1',
+};
diff --git a/src/utils/constant.js b/src/utils/constant.js
deleted file mode 100644
index 3ca2222..0000000
--- a/src/utils/constant.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const ROLE_TYPE = {
- ADMIN: 'admin',
- DEFAULT: 'admin',
- DEVELOPER: 'developer',
-}
-
-export const CANCEL_REQUEST_MESSAGE = 'cancel request'
diff --git a/src/utils/iconMap.jsx b/src/utils/iconMap.jsx
deleted file mode 100644
index 0db72bc..0000000
--- a/src/utils/iconMap.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- PayCircleOutlined,
- ShoppingCartOutlined,
- MessageOutlined,
- TeamOutlined,
- UserOutlined,
- DashboardOutlined,
- ApiOutlined,
- CameraOutlined,
- EditOutlined,
- CodeOutlined,
- LineOutlined,
- BarChartOutlined,
- AreaChartOutlined,
-} from '@ant-design/icons'
-
-const iconMap = {
- 'pay-circle-o': ,
- 'shopping-cart': ,
- 'camera-o': ,
- 'line-chart': ,
- 'code-o': ,
- 'area-chart': ,
- 'bar-chart': ,
- message: ,
- team: ,
- dashboard: ,
- user: ,
- api: ,
- edit: ,
-}
-
-export default iconMap
diff --git a/src/utils/index.js b/src/utils/index.js
deleted file mode 100644
index 99d3a6e..0000000
--- a/src/utils/index.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import { cloneDeep } from 'lodash'
-import { pathToRegexp } from 'path-to-regexp'
-import store from 'store'
-import { i18n } from '@lingui/core'
-
-import dayjs from 'dayjs'
-import relativeTime from 'dayjs/plugin/relativeTime'
-import 'dayjs/locale/zh-cn'
-
-dayjs.extend(relativeTime)
-
-export classnames from 'classnames'
-export config from './config'
-export request from './request'
-export { Color } from './theme'
-
-export const languages = i18n && i18n.languages ? i18n.languages.map(item => item.key) : []
-export const defaultLanguage = i18n && i18n.defaultLanguage ? i18n.defaultLanguage : 'en'
-
-/**
- * Query objects that specify keys and values in an array where all values are objects.
- * @param {array} array An array where all values are objects, like [{key:1},{key:2}].
- * @param {string} key The key of the object that needs to be queried.
- * @param {string} value The value of the object that needs to be queried.
- * @return {object|undefined} Return frist object when query success.
- */
-export function queryArray(array, key, value) {
- if (!Array.isArray(array)) {
- return
- }
- return array.find(_ => _[key] === value)
-}
-
-/**
- * Convert an array to a tree-structured array.
- * @param {array} array The Array need to Converted.
- * @param {string} id The alias of the unique ID of the object in the array.
- * @param {string} parentId The alias of the parent ID of the object in the array.
- * @param {string} children The alias of children of the object in the array.
- * @return {array} Return a tree-structured array.
- */
-export function arrayToTree(
- array,
- id = 'id',
- parentId = 'pid',
- children = 'children'
-) {
- const result = []
- const hash = {}
- const data = cloneDeep(array)
-
- data.forEach((item, index) => {
- hash[data[index][id]] = data[index]
- })
-
- data.forEach(item => {
- const hashParent = hash[item[parentId]]
- if (hashParent) {
- !hashParent[children] && (hashParent[children] = [])
- hashParent[children].push(item)
- } else {
- result.push(item)
- }
- })
- return result
-}
-
-
-
-/**
- * In an array object, traverse all parent IDs based on the value of an object.
- * @param {array} array The Array need to Converted.
- * @param {string} current Specify the value of the object that needs to be queried.
- * @param {string} parentId The alias of the parent ID of the object in the array.
- * @param {string} id The alias of the unique ID of the object in the array.
- * @return {array} Return a key array.
- */
-export function queryPathKeys(array, current, parentId, id = 'id') {
- const result = [current]
- const hashMap = new Map()
- array.forEach(item => hashMap.set(item[id], item))
-
- const getPath = current => {
- const currentParentId = hashMap.get(current)[parentId]
- if (currentParentId) {
- result.push(currentParentId)
- getPath(currentParentId)
- }
- }
-
- getPath(current)
- return result
-}
-
-/**
- * In an array of objects, specify an object that traverses the objects whose parent ID matches.
- * @param {array} array The Array need to Converted.
- * @param {string} current Specify the object that needs to be queried.
- * @param {string} parentId The alias of the parent ID of the object in the array.
- * @param {string} id The alias of the unique ID of the object in the array.
- * @return {array} Return a key array.
- */
-export function queryAncestors(array, current, parentId, id = 'id') {
- const result = [current]
- const hashMap = new Map()
- array.forEach(item => hashMap.set(item[id], item))
-
- const getPath = current => {
- const currentParentId = hashMap.get(current[id])[parentId]
- if (currentParentId) {
- result.push(hashMap.get(currentParentId))
- getPath(hashMap.get(currentParentId))
- }
- }
-
- getPath(current)
- return result
-}
-
-/**
- * Query which layout should be used for the current path based on the configuration.
- * @param {layouts} layouts Layout configuration.
- * @param {pathname} pathname Path name to be queried.
- * @return {string} Return frist object when query success.
- */
-export function queryLayout(layouts, pathname) {
- let result = 'public'
-
- const isMatch = regepx => {
- return regepx instanceof RegExp
- ? regepx.test(pathname)
- : pathToRegexp(regepx).exec(pathname)
- }
-
- for (const item of layouts) {
- let include = false
- let exclude = false
- if (item.include) {
- for (const regepx of item.include) {
- if (isMatch(regepx)) {
- include = true
- break
- }
- }
- }
-
- if (include && item.exclude) {
- for (const regepx of item.exclude) {
- if (isMatch(regepx)) {
- exclude = true
- break
- }
- }
- }
-
- if (include && !exclude) {
- result = item.name
- break
- }
- }
-
- return result
-}
-
-
-export function getLocale() {
- return store.get('locale') || defaultLanguage
-}
-
-export function setLocale(language) {
- if (getLocale() !== language) {
- dayjs.locale(language === 'zh' ? 'zh-cn' : language)
- store.set('locale', language)
- window.location.reload()
- }
-}
diff --git a/src/utils/index.test.js b/src/utils/index.test.js
deleted file mode 100644
index d9a3070..0000000
--- a/src/utils/index.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { pathToRegexp } from 'path-to-regexp'
-
-// strip known locale prefix (e.g. /zh, /en, /pt-br) before matching
-const stripLocale = (p) => p.replace(/^\/(en|zh|pt-br)(\/|$)/, '/')
-
-describe('test pathToRegexp', () => {
- it('get right', () => {
- expect(pathToRegexp('/user').exec(stripLocale('/zh/user'))).toEqual(
- pathToRegexp('/user').exec('/user')
- )
- expect(pathToRegexp('/user').exec('/user')).toEqual(
- pathToRegexp('/user').exec('/user')
- )
-
- expect(pathToRegexp('/user/:id').exec(stripLocale('/zh/user/1'))).toEqual(
- pathToRegexp('/user/:id').exec('/user/1')
- )
- expect(pathToRegexp('/user/:id').exec('/user/1')).toEqual(
- pathToRegexp('/user/:id').exec('/user/1')
- )
- })
-})
diff --git a/src/utils/model.js b/src/utils/model.js
deleted file mode 100644
index 6bc8b9b..0000000
--- a/src/utils/model.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import modelExtend from 'dva-model-extend'
-
-export const model = {
- reducers: {
- updateState(state, { payload }) {
- return {
- ...state,
- ...payload,
- }
- },
- },
-}
-
-export const pageModel = modelExtend(model, {
- state: {
- list: [],
- pagination: {
- showSizeChanger: true,
- showQuickJumper: true,
- current: 1,
- total: 0,
- pageSize: 10,
- },
- },
-
- reducers: {
- querySuccess(state, { payload }) {
- const { list, pagination } = payload
- return {
- ...state,
- list,
- pagination: {
- ...state.pagination,
- ...pagination,
- },
- }
- },
- },
-})
diff --git a/src/utils/request.js b/src/utils/request.js
deleted file mode 100644
index 188115f..0000000
--- a/src/utils/request.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import axios from 'axios'
-import { cloneDeep } from 'lodash'
-import { parse, compile } from 'path-to-regexp'
-import { message } from 'antd'
-import { CANCEL_REQUEST_MESSAGE } from 'utils/constant'
-
-window.cancelRequest = new Map()
-
-export default function request(options) {
- let { data, url } = options
- const cloneData = cloneDeep(data)
-
- try {
- let domain = ''
- const urlMatch = url.match(/[A-Za-z]+:\/\/[^/]*/)
- if (urlMatch) {
- ;[domain] = urlMatch
- url = url.slice(domain.length)
- }
-
- const match = parse(url)
- url = compile(url)(data)
-
- for (const item of match) {
- if (item instanceof Object && item.name in cloneData) {
- delete cloneData[item.name]
- }
- }
- url = domain + url
- } catch (e) {
- message.error(e.message)
- }
-
- options.url = url
- // Use AbortController signal for request cancellation (compatible with axios v2)
- const controller = new AbortController()
- options.signal = controller.signal
- window.cancelRequest.set(Symbol(Date.now()), {
- pathname: window.location.pathname,
- abort: reason => controller.abort(reason || CANCEL_REQUEST_MESSAGE),
- })
-
- return axios(options)
- .then(response => {
- const { statusText, status, data } = response
-
- let result = {}
- if (typeof data === 'object') {
- result = data
- if (Array.isArray(data)) {
- result.list = data
- }
- } else {
- result.data = data
- }
-
- return Promise.resolve({
- success: true,
- message: statusText,
- statusCode: status,
- ...result,
- })
- })
- .catch(error => {
- const { response, message: errMsg, code, name } = error
-
- // axios v1 cancellation can surface as ERR_CANCELED / CanceledError or message matching our sentinel
- if (String(errMsg) === CANCEL_REQUEST_MESSAGE || code === 'ERR_CANCELED' || name === 'CanceledError') {
- return {
- success: false,
- }
- }
-
- let msg
- let statusCode
-
- if (response && response instanceof Object) {
- const { data, statusText } = response
- statusCode = response.status
- msg = data.message || statusText
- } else {
- statusCode = 600
- msg = errMsg || error.message || 'Network Error'
- }
-
- /* eslint-disable */
- return Promise.reject({
- success: false,
- statusCode,
- message: msg,
- })
- })
-}
diff --git a/src/utils/request.test.ts b/src/utils/request.test.ts
new file mode 100644
index 0000000..cf9aa40
--- /dev/null
+++ b/src/utils/request.test.ts
@@ -0,0 +1,62 @@
+import { message } from 'antd';
+import api from './request';
+
+describe('api', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ (global as any).fetch = jest.fn();
+ localStorage.clear();
+ });
+
+ test('successful response and query params', async () => {
+ (global.fetch as jest.Mock).mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => ({ success: true, data: { id: 1 } }),
+ });
+ const data = await api<{ id: number }>('/items', { params: { q: 'a' } });
+ expect(data).toEqual({ id: 1 });
+ expect(global.fetch).toBeCalledWith(
+ '/api/v1/items?q=a',
+ expect.any(Object),
+ );
+ });
+
+ test('injects Authorization header from localStorage', async () => {
+ localStorage.setItem('app-auth-token', 'tok');
+ let capturedReq: any;
+ (global.fetch as jest.Mock).mockImplementation((url, opts) => {
+ capturedReq = opts;
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ json: async () => ({ success: true, data: null }),
+ });
+ });
+ await api('/me', {});
+ expect(capturedReq.headers.Authorization).toBe('Bearer tok');
+ });
+
+ test('network error shows message and throws', async () => {
+ const err = new Error('net fail');
+ (global.fetch as jest.Mock).mockRejectedValue(err);
+ const spy = jest.spyOn(message, 'error') as jest.SpyInstance;
+ spy.mockImplementation(() => ({} as any));
+ await expect(api('/x')).rejects.toThrow('net fail');
+ expect(spy).toBeCalledWith('net fail');
+ spy.mockRestore();
+ });
+
+ test('invalid response format shows message and throws', async () => {
+ (global.fetch as jest.Mock).mockResolvedValue({
+ ok: true,
+ status: 200,
+ json: async () => ({}),
+ });
+ const spy = jest.spyOn(message, 'error') as jest.SpyInstance;
+ spy.mockImplementation(() => ({} as any));
+ await expect(api('/x')).rejects.toThrow('Invalid API response format');
+ expect(spy).toBeCalledWith('Invalid API response format');
+ spy.mockRestore();
+ });
+});
diff --git a/src/utils/request.ts b/src/utils/request.ts
new file mode 100644
index 0000000..6f1c3e9
--- /dev/null
+++ b/src/utils/request.ts
@@ -0,0 +1,127 @@
+import { message } from 'antd';
+
+import config from './config';
+
+/** 原始值类型 */
+export type Primitive = string | number | boolean;
+
+/**
+ * 请求选项
+ * - `params` 会被序列化为查询字符串
+ * - `body` 会在非 FormData 情况下 JSON 序列化并设置 Content-Type
+ */
+export type ApiOptions = Omit & {
+ params?: Record;
+ body?: unknown;
+};
+
+/** 约定的后端返回格式 */
+export type ApiResponse = {
+ success: boolean;
+ message?: string;
+ data: T;
+};
+
+/**
+ * 统一的 fetch 封装
+ *
+ * 行为说明(对 LLM/开发者友好说明):
+ * - 将 `config.apiPrefix` 与 `path` 拼接为最终 URL。
+ * - 当 `options.params` 存在时,转为查询字符串并添加到 URL 上。
+ * - `body` 处理:
+ * - 如果是 FormData,直接交给 fetch 处理(不要手动设置 Content-Type)。
+ * - 如果是字符串或 Blob,直接发送。
+ * - 其他对象会被 JSON.stringify,并默认设置 `Content-Type: application/json`(除非已在 headers 中指定)。
+ * - 当响应状态为 204 时返回空对象;否则尝试解析响应为 JSON。
+ * - 当响应不 OK(res.ok === false)时抛出 Error,优先使用响应体的 `message` 字段作为错误信息。
+ *
+ * 使用示例:
+ * ```ts
+ * const data = await api<{ id: number }>(`/items`, { method: 'POST', body: { name: 'a' } });
+ * ```
+ */
+export async function api(
+ path: string,
+ options: ApiOptions = {},
+): Promise {
+ const { params, body, headers: customHeaders, ...rest } = options;
+
+ // 构建 url 并附加查询参数
+ let url = `${config.apiPrefix}${path}`;
+ if (params && Object.keys(params).length) {
+ const qp: Record = {};
+ Object.entries(params).forEach(([k, v]) => {
+ qp[k] = String(v);
+ });
+ const qs = new URLSearchParams(qp).toString();
+ url += url.includes('?') ? `&${qs}` : `?${qs}`;
+ }
+
+ const headers: Record = {
+ ...((customHeaders as Record) || {}),
+ };
+ let bodyToSend: BodyInit | undefined;
+ if (body !== undefined) {
+ // If body is FormData, let fetch handle headers.
+ if (typeof FormData !== 'undefined' && body instanceof FormData) {
+ bodyToSend = body as BodyInit;
+ } else if (typeof body === 'string' || body instanceof Blob) {
+ bodyToSend = body as BodyInit;
+ } else {
+ // For object payloads, default to JSON content-type
+ headers['Content-Type'] = headers['Content-Type'] || 'application/json';
+ try {
+ bodyToSend = JSON.stringify(body);
+ } catch (e) {
+ bodyToSend = undefined;
+ }
+ }
+ }
+
+ // inject auth token if present
+ try {
+ const token =
+ typeof window !== 'undefined'
+ ? localStorage.getItem('app-auth-token')
+ : null;
+ if (token && !headers.Authorization && !headers.authorization) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+ } catch (e) {
+ // ignore storage access errors
+ }
+
+ // perform request with network error handling
+ let res: Response;
+ try {
+ res = await fetch(url, { headers, body: bodyToSend, ...rest });
+ } catch (err: any) {
+ const netMsg = err?.message || 'Network error';
+ message.error(netMsg);
+ throw new Error(netMsg);
+ }
+
+ const json = await res.json().catch(() => null);
+
+ // 后端返回固定格式:{ success: boolean, message?: string, data: T }
+ if (
+ !json ||
+ typeof json !== 'object' ||
+ typeof (json as any).success !== 'boolean'
+ ) {
+ const invalidMsg = 'Invalid API response format';
+ message.error(invalidMsg);
+ throw new Error(invalidMsg);
+ }
+ const parsed = json as ApiResponse;
+
+ if (!res.ok || !parsed.success) {
+ const msg = parsed.message || `Request failed: ${res.status}`;
+ message.error(msg);
+ throw new Error(msg);
+ }
+
+ return parsed.data as T;
+}
+
+export default api;
diff --git a/src/utils/theme.js b/src/utils/theme.js
deleted file mode 100644
index 0a610df..0000000
--- a/src/utils/theme.js
+++ /dev/null
@@ -1,14 +0,0 @@
-module.exports = {
- Color: {
- green: '#64ea91',
- blue: '#8fc9fb',
- purple: '#d897eb',
- red: '#f69899',
- yellow: '#f8c82e',
- peach: '#f797d6',
- borderBase: '#e5e5e5',
- borderSplit: '#f4f4f4',
- grass: '#d6fbb5',
- sky: '#c1e0fc',
- },
-}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..62a0c7c
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "./src/.umi/tsconfig.json",
+ "compilerOptions": {
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "forceConsistentCasingInFileNames": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "jsx": "react-jsx"
+ }
+}
diff --git a/typings.d.ts b/typings.d.ts
new file mode 100644
index 0000000..9ae2fc4
--- /dev/null
+++ b/typings.d.ts
@@ -0,0 +1 @@
+import 'umi/typings';