├── .babelrc ├── .commitlintrc.json ├── .editorconfig ├── .env.deploy ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── privew-deloy.yml │ └── prview-build.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc ├── Develop-problem-change.md ├── README.md ├── build ├── env.js ├── fileList.js ├── upload.js ├── webpack.config.js ├── webpack.dev.js └── webpack.prod.js ├── declaration.d.ts ├── deploy.sh ├── material ├── menus.png └── menus.xmind ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public └── index.html ├── server ├── index.js └── logger.js ├── src ├── App.tsx ├── Layout │ ├── Bread │ │ └── Bread.tsx │ ├── Header │ │ ├── Header.tsx │ │ └── index.module.less │ ├── Layout.tsx │ ├── MainRoute.tsx │ ├── Menu.tsx │ └── index.less ├── ahooks │ ├── index.ts │ └── useI18n.ts ├── app │ ├── Dashboard │ │ ├── Monitor │ │ │ └── Monitor.tsx │ │ └── WorkPlace │ │ │ ├── WorkPlace.tsx │ │ │ ├── index.module.less │ │ │ ├── mock │ │ │ └── index.ts │ │ │ └── modules │ │ │ ├── chart │ │ │ └── conentChart.tsx │ │ │ ├── config │ │ │ └── index.ts │ │ │ └── slide │ │ │ ├── ContentData.tsx │ │ │ ├── Shortcuts.tsx │ │ │ ├── banner.tsx │ │ │ ├── contentData.tsx │ │ │ ├── contentView.tsx │ │ │ ├── overview.tsx │ │ │ ├── shortcuts.tsx │ │ │ └── style │ │ │ ├── content.module.less │ │ │ ├── overview.module.less │ │ │ └── shortcuts.less │ ├── Exception │ │ ├── 403.tsx │ │ ├── 404.tsx │ │ ├── 500.tsx │ │ ├── Building.tsx │ │ └── locales │ │ │ ├── en-us.ts │ │ │ ├── index.ts │ │ │ └── zh-cn.ts │ ├── Home │ │ └── index.tsx │ ├── Login │ │ ├── index.module.less │ │ ├── index.tsx │ │ ├── locales │ │ │ ├── en-us.ts │ │ │ ├── index.ts │ │ │ └── zh-cn.ts │ │ ├── mock │ │ │ └── user.ts │ │ └── modules │ │ │ └── Banner.tsx │ ├── Result │ │ ├── Error.tsx │ │ ├── Success.tsx │ │ └── locales │ │ │ ├── en-us.ts │ │ │ ├── index.ts │ │ │ └── zh-cn.ts │ ├── User │ │ ├── Info │ │ │ ├── Info.tsx │ │ │ └── index.less │ │ └── setting │ │ │ ├── index.module.less │ │ │ └── index.tsx │ ├── Visualization │ │ ├── DataAnalysis │ │ │ └── index.tsx │ │ └── MultiDimensionDataAnalysis │ │ │ └── index.tsx │ └── Welcome │ │ ├── index.module.less │ │ └── index.tsx ├── assets │ ├── css │ │ ├── comom.css │ │ ├── index.css │ │ ├── nprogress.less │ │ ├── reset-arco.css │ │ └── reset.css │ └── images │ │ └── github.svg ├── components │ ├── CommonSetting │ │ └── index.tsx │ ├── Loading │ │ └── Loading.tsx │ └── PageConifg │ │ └── PageConfig.tsx ├── conifg │ ├── index.ts │ ├── menuConfig.tsx │ └── routerConfig.tsx ├── index.less ├── index.tsx ├── locales │ ├── en-us.ts │ ├── index.ts │ └── zh-cn.ts ├── mock │ └── index.ts └── utils │ ├── GlobalContext.tsx │ ├── index.ts │ └── setupMock.ts ├── template ├── index.html └── js │ └── index.js └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 5 | ["@babel/plugin-proposal-class-properties", { "legacy": true, "loose": false }], 6 | [ 7 | "babel-plugin-import", 8 | { 9 | "libraryName": "@arco-design/web-react", 10 | "libraryDirectory": "es", 11 | "camel2DashComponentName": false, 12 | "style": true // 样式按需加载 13 | } 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"], 3 | "rules": { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | ["bug", "feat", "fix", "docs", "style", "refactor", "test", "chore", "revert", "merge"] 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.env.deploy: -------------------------------------------------------------------------------- 1 | NODE_ENV=deploy -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | REACT_APP_SERVER_DOMAIN=http://framework.yaogeng.com.cn:8080 3 | REACT_APP_TARGETURL='' // 代理地址 -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | REACT_APP_SERVER_DOMAIN=https:yaogeng.top -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV=test 2 | REACT_APP_SERVER_DOMAIN=http://framework.yaogeng.com.cn 3 | REACT_APP_TARGETURL='' // 代理地址 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | public/* 3 | template/* 4 | /*.js 5 | /*.d.ts 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | }, 7 | parser: 'babel-eslint', 8 | extends: ['eslint:recommended', 'plugin:react/recommended'], 9 | parserOptions: { 10 | ecmaVersion: 7, 11 | // 开启实验属性 12 | ecmaFeatures: { 13 | experimentalObjectRestSpread: true, 14 | // 修饰器 15 | experimentalDecorators: true, 16 | jsx: true, 17 | }, 18 | sourceType: 'module', 19 | }, 20 | plugins: ['react', '@typescript-eslint'], 21 | globals: { 22 | process: false, 23 | __DEV__: false, 24 | __dirname: false, 25 | window: true, 26 | define: true, 27 | history: true, 28 | location: true, 29 | wxjs: true, 30 | $: true, 31 | WeixinJSBridge: true, 32 | wx: true, 33 | qq: true, 34 | }, 35 | settings: { 36 | react: { 37 | version: '17.0.2', 38 | }, 39 | }, 40 | /** 41 | * "off" 或 0 - 关闭规则 42 | * "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出), 43 | * "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出) 44 | */ 45 | rules: { 46 | 'no-cond-assign': 2, 47 | 'no-console': [ 48 | 'error', 49 | { 50 | allow: ['log', 'warn', 'error', 'info'], 51 | }, 52 | ], 53 | // 禁止 function 定义中出现重名参数 54 | 'no-dupe-args': 2, 55 | // 禁止对象字面量中出现重复的 key 56 | 'no-dupe-keys': 2, 57 | // 禁止重复的 case 标签 58 | 'no-duplicate-case': 2, 59 | // 禁止空语句块 60 | 'no-empty': 2, 61 | // 禁止对 catch 子句的参数重新赋值 62 | 'no-ex-assign': 2, 63 | // 禁止不必要的布尔转换 64 | 'no-extra-boolean-cast': 2, 65 | // 禁止不必要的括号 //(a * b) + c;//报错 66 | 'no-extra-parens': 0, 67 | 68 | // 强制所有控制语句使用一致的括号风格 69 | curly: ["error", "multi-line"], 70 | // 禁止 catch 子句的参数与外层作用域中的变量同名 71 | 'no-catch-shadow': 0, 72 | // 不允许标签与变量同名 73 | 'no-label-var': 2, 74 | // 禁用特定的全局变量 75 | 'no-restricted-globals': 2, 76 | // 禁止 var 声明 与外层作用域的变量同名 77 | 'no-shadow': 0, 78 | // 禁止覆盖受限制的标识符 79 | 'no-shadow-restricted-names': 2, 80 | // 禁止将变量初始化为 undefined 81 | 'no-undef-init': 2, 82 | // 禁止将 undefined 作为标识符 83 | 'no-undefined': 0, 84 | // 不允许在变量定义之前使用它们 85 | 'no-use-before-define': 0, 86 | // //////////// 87 | // 风格指南 // 88 | // //////////// 89 | // 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格 90 | 'array-bracket-spacing': [2, 'never'], 91 | // 禁止或强制在单行代码块中使用空格(禁用) 92 | 'block-spacing': [1, 'never'], 93 | // 强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab, 94 | // if while function 后面的{必须与if在同一行,java风格。 95 | 'brace-style': [ 96 | 2, 97 | '1tbs', 98 | { 99 | allowSingleLine: true, 100 | }, 101 | ], 102 | // 控制逗号前后的空格 103 | 'comma-spacing': [ 104 | 2, 105 | { 106 | before: false, 107 | after: true, 108 | }, 109 | ], 110 | // 控制逗号在行尾出现还是在行首出现 (默认行尾) 111 | // http://eslint.org/docs/rules/comma-style 112 | 'comma-style': [2, 'last'], 113 | // "SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平 114 | // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always 115 | 'computed-property-spacing': [2, 'never'], 116 | // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了 117 | // e.g [0,"self"] 指定只能 var that = this. self不能指向其他任何值,this也不能赋值给self以外的其他值 118 | 'consistent-this': [2, 'self', 'that', '_self', '_that', 'me', '_this'], 119 | // 强制使用命名的 function 表达式 120 | 'func-names': 0, 121 | // 文件末尾强制换行 122 | 'eol-last': 2, 123 | indent: ['error', 2, { "SwitchCase": 1 }], 124 | // 要求或禁止在函数标识符和其调用之间有空格 125 | 'func-call-spacing': 2, 126 | // 强制在对象字面量的属性中键和值之间使用一致的间距 127 | 'key-spacing': [ 128 | 2, 129 | { 130 | beforeColon: false, 131 | afterColon: true, 132 | }, 133 | ], 134 | // 要求在注释周围有空行 ( 要求在块级注释之前有一空行) 135 | 'lines-around-comment': [ 136 | 2, 137 | { 138 | beforeBlockComment: true, 139 | }, 140 | ], 141 | 'func-style': 0, 142 | // 强制回调函数最大嵌套深度 5层 143 | 'max-nested-callbacks': [2, 5], 144 | // 禁止使用指定的标识符 145 | 'id-blacklist': 0, 146 | // 强制标识符的最新和最大长度 147 | 'id-length': 0, 148 | // 要求标识符匹配一个指定的正则表达式 149 | 'id-match': 0, 150 | // 强制在 JSX 属性中一致地使用双引号或单引号 151 | 'jsx-quotes': 0, 152 | // 强制在关键字前后使用一致的空格 (前后腰需要) 153 | 'keyword-spacing': 2, 154 | // 强制一行的最大长度 155 | 'max-len': [2, 200, { ignoreUrls: true }], 156 | // 强制最大行数 157 | 'max-lines': 0, 158 | // 强制 function 定义中最多允许的参数数量 159 | 'max-params': [1, 5], 160 | // 强制 function 块最多允许的的语句数量 161 | 'max-statements': [1, 200], 162 | // 强制每一行中所允许的最大语句数量 163 | 'max-statements-per-line': 0, 164 | // 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。) 165 | 'new-cap': [ 166 | 2, 167 | { 168 | newIsCap: true, 169 | capIsNew: false, 170 | }, 171 | ], 172 | // 要求调用无参构造函数时有圆括号 173 | 'new-parens': 2, 174 | // 要求或禁止 var 声明语句后有一行空行 175 | 'newline-after-var': 0, 176 | // 禁止使用 Array 构造函数 177 | 'no-array-constructor': 2, 178 | // 禁用按位运算符 179 | 'no-bitwise': 0, 180 | // 要求 return 语句之前有一空行 181 | 'newline-before-return': 0, 182 | // 要求方法链中每个调用都有一个换行符 183 | 'newline-per-chained-call': 1, 184 | // 禁用 continue 语句 185 | 'no-continue': 0, 186 | // 禁止在代码行后使用内联注释 187 | 'no-inline-comments': 0, 188 | // 禁止 if 作为唯一的语句出现在 else 语句中 189 | 'no-lonely-if': 0, 190 | // 禁止混合使用不同的操作符 191 | 'no-mixed-operators': 0, 192 | // 禁止空格和 tab 的混合缩进 193 | 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], 194 | // 不允许多个空行 195 | 'no-multiple-empty-lines': [ 196 | 2, 197 | { 198 | max: 2, 199 | }, 200 | ], 201 | // 不允许否定的表达式 202 | 'no-negated-condition': 0, 203 | // 不允许使用嵌套的三元表达式 204 | 'no-nested-ternary': 0, 205 | // 禁止使用 Object 的构造函数 206 | 'no-new-object': 2, 207 | // 禁止使用一元操作符 ++ 和 -- 208 | 'no-plusplus': 0, 209 | // 禁止使用特定的语法 210 | 'no-restricted-syntax': 0, 211 | // 禁止 function 标识符和括号之间出现空格 212 | 'no-spaced-func': 2, 213 | // 不允许使用三元操作符 214 | 'no-ternary': 0, 215 | // 禁用行尾空格 216 | 'no-trailing-spaces': 2, 217 | // 禁止标识符中有悬空下划线_bar 218 | 'no-underscore-dangle': 0, 219 | // 禁止可以在有更简单的可替代的表达式时使用三元操作符 220 | 'no-unneeded-ternary': 2, 221 | // 禁止属性前有空白 222 | 'no-whitespace-before-property': 2, 223 | // 要求或禁止在 var 声明周围换行 224 | 'one-var-declaration-per-line': 0, 225 | // 要求或禁止在可能的情况下要求使用简化的赋值操作符 226 | 'operator-assignment': 0, 227 | // 强制操作符使用一致的换行符 228 | 'operator-linebreak': [ 229 | 2, 230 | 'after', 231 | { 232 | overrides: { 233 | '?': 'before', 234 | ':': 'before', 235 | }, 236 | }, 237 | ], 238 | // 要求或禁止块内填充 239 | 'padded-blocks': 0, 240 | // 要求对象字面量属性名称用引号括起来 241 | 'quote-props': 0, 242 | // 强制使用一致的反勾号、双引号或单引号 243 | quotes: [2, 'single', 'avoid-escape'], 244 | // 要求使用 JSDoc 注释 245 | 'require-jsdoc': 0, 246 | // 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,) 247 | // "semi": [2, "always"], 248 | // 强制分号之前和之后使用一致的空格 249 | 'semi-spacing': 2, 250 | // 要求同一个声明块中的变量按顺序排列 251 | 'sort-vars': 0, 252 | // 强制在块之前使用一致的空格 253 | 'space-before-blocks': [2, 'always'], 254 | // 强制在 function的左括号之前使用一致的空格 255 | 'space-before-function-paren': [0, 'always'], 256 | // 强制在圆括号内使用一致的空格 257 | 'space-in-parens': [2, 'never'], 258 | // 要求操作符周围有空格 259 | 'space-infix-ops': 2, 260 | // 强制在一元操作符前后使用一致的空格 261 | 'space-unary-ops': [ 262 | 2, 263 | { 264 | words: true, 265 | nonwords: false, 266 | }, 267 | ], 268 | // 强制在注释中 // 或 /* 使用一致的空格 269 | 'spaced-comment': [ 270 | 2, 271 | 'always', 272 | { 273 | markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!'], 274 | }, 275 | ], 276 | // 要求或禁止 Unicode BOM 277 | 'unicode-bom': 2, 278 | // 要求正则表达式被括号括起来 279 | 'wrap-regex': 0, 280 | // 禁止词法声明 (let、const、function 和 class) 出现在 case或default 子句中 281 | 'no-case-declarations': ['warn'], 282 | 283 | // //////////// 284 | // ES6.相关 // 285 | // //////////// 286 | // 要求箭头函数体使用大括号 287 | // 'arrow-body-style': 2, 288 | // 要求箭头函数的参数使用圆括号 289 | 'arrow-parens': 0, 290 | 'arrow-spacing': [ 291 | 2, 292 | { 293 | before: true, 294 | after: true, 295 | }, 296 | ], 297 | // 强制 generator 函数中 * 号周围使用一致的空格 298 | 'generator-star-spacing': [ 299 | 2, 300 | { 301 | before: true, 302 | after: true, 303 | }, 304 | ], 305 | // 禁止修改类声明的变量 306 | 'no-class-assign': 2, 307 | // 不允许箭头功能,在那里他们可以混淆的比较 308 | 'no-confusing-arrow': 0, 309 | // 禁止修改 const 声明的变量 310 | 'no-const-assign': 2, 311 | // 禁止类成员中出现重复的名称 312 | 'no-dupe-class-members': 2, 313 | // 每个模块只能使用一个import 314 | 'no-duplicate-imports': 2, 315 | // 禁止 Symbolnew 操作符和 new 一起使用 316 | 'no-new-symbol': 2, 317 | // 允许指定模块加载时的进口 318 | 'no-restricted-imports': 0, 319 | // 禁止在构造函数中,在调用 super() 之前使用 this 或 super 320 | 'no-this-before-super': 2, 321 | // 禁止不必要的计算性能键对象的文字 322 | 'no-useless-computed-key': 0, 323 | // 要求使用 let 或 const 而不是 var 324 | 'no-var': 1, 325 | // 要求或禁止对象字面量中方法和属性使用简写语法 326 | 'object-shorthand': 0, 327 | // 要求使用箭头函数作为回调 328 | 'prefer-arrow-callback': 0, 329 | // 要求使用 const 声明那些声明后不再被修改的变量 330 | 'prefer-const': 0, 331 | // 要求在合适的地方使用 Reflect 方法 332 | 'prefer-reflect': 0, 333 | // 要求使用扩展运算符而非 .apply() 334 | 'prefer-spread': 0, 335 | // 要求使用模板字面量而非字符串连接 336 | 'prefer-template': 0, 337 | // Suggest using the rest parameters instead of arguments 338 | 'prefer-rest-params': 0, 339 | // 要求generator 函数内有 yield 340 | 'require-yield': 2, 341 | // 要求或禁止模板字符串中的嵌入表达式周围空格的使用 342 | 'template-curly-spacing': 1, 343 | // 强制在 yield* 表达式中 * 周围使用空格 344 | 'yield-star-spacing': 2, 345 | 346 | // 强制使用一致的换行风格 347 | 'linebreak-style': [0, 'unix'], 348 | // 在JSX中强制布尔属性符号 349 | 'react/jsx-boolean-value': 2, 350 | // 在JSX中验证右括号位置 351 | // "react/jsx-closing-bracket-location": 1, 352 | // 在JSX属性和表达式中加强或禁止大括号内的空格。 353 | 'react/jsx-curly-spacing': [ 354 | 2, 355 | { 356 | when: 'never', 357 | children: true, 358 | }, 359 | ], 360 | // 在数组或迭代器中验证JSX具有key属性 361 | 'react/jsx-key': 2, 362 | // 限制JSX中单行上的props的最大数量 363 | 'react/jsx-max-props-per-line': [ 364 | 1, 365 | { 366 | maximum: 5, 367 | }, 368 | ], 369 | // 防止在JSX中重复的props 370 | 'react/jsx-no-duplicate-props': 2, 371 | // //防止使用未包装的JSX字符串 372 | // "react/jsx-no-literals": 0, 373 | // 在JSX中禁止未声明的变量 374 | 'react/jsx-no-undef': 2, 375 | // 为用户定义的JSX组件强制使用PascalCase 376 | 'react/jsx-pascal-case': 0, 377 | // 防止反应被错误地标记为未使用 378 | 'react/jsx-uses-react': 2, 379 | // 防止在JSX中使用的变量被错误地标记为未使用 380 | 'react/jsx-uses-vars': 2, 381 | // 防止在componentDidMount中使用setState 382 | 'react/no-did-mount-set-state': 2, 383 | // 防止在componentDidUpdate中使用setState 384 | 'react/no-did-update-set-state': 2, 385 | // 防止使用未知的DOM属性 386 | 'react/no-unknown-property': 2, 387 | // 为React组件强制执行ES5或ES6类 388 | 'react/prefer-es6-class': 2, 389 | // 防止在React组件定义中丢失props验证 390 | 'react/prop-types': 0, 391 | // 使用JSX时防止丢失React 392 | 'react/react-in-jsx-scope': 2, 393 | // 防止没有children的组件的额外结束标签 394 | 'react/self-closing-comp': 0, 395 | // 禁止不必要的bool转换 396 | // "no-extra-boolean-cast": 0, 397 | // 防止在数组中遍历中使用数组key做索引 398 | // "react/no-array-index-key": 0, 399 | // 不使用弃用的方法 400 | 'react/no-deprecated': 2, 401 | // 在JSX属性中强制或禁止等号周围的空格 402 | 'react/jsx-equals-spacing': 2, 403 | 'react/jsx-filename-extension': [ 404 | 2, 405 | { 406 | extensions: ['.js', '.jsx', 'tsx', 'ts'], 407 | }, 408 | ], 409 | // 禁止未使用的变量 410 | 'no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: false }], 411 | }, 412 | } 413 | -------------------------------------------------------------------------------- /.github/workflows/privew-deloy.yml: -------------------------------------------------------------------------------- 1 | name: Preview Deploy 2 | 3 | on: 4 | workflow_run: 5 | workflows: ['Preview Build'] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | success: 11 | runs-on: ubuntu-latest 12 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' 13 | steps: 14 | - name: download pr artifact 15 | uses: dawidd6/action-download-artifact@v2 16 | with: 17 | workflow: ${{ github.event.workflow_run.workflow_id }} 18 | name: pr 19 | - name: save PR id 20 | id: pr 21 | run: echo "::set-output name=id::$(完成 44 | 45 | failed: 46 | runs-on: ubuntu-latest 47 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' 48 | steps: 49 | - name: download pr artifact 50 | uses: dawidd6/action-download-artifact@v2 51 | with: 52 | workflow: ${{ github.event.workflow_run.workflow_id }} 53 | name: pr 54 | - name: save PR id 55 | id: pr 56 | run: echo "::set-output name=id::$( ./pr-id.txt 40 | 41 | - name: Upload PR number 42 | if: ${{ always() }} 43 | uses: actions/upload-artifact@v2 44 | with: 45 | name: pr 46 | path: ./pr-id.txt 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | public 4 | /dist 5 | .env 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | # Ignore all JS files: -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "semi": false, 6 | "useTabs": false, 7 | "bracketSpacing": true, 8 | "trailingComma": "all", 9 | "jsxSingleQuote": false 10 | } -------------------------------------------------------------------------------- /Develop-problem-change.md: -------------------------------------------------------------------------------- 1 | ### css Module 处理 2 | 3 | > 问题: 4 | 5 | 解决css冲突 6 | 7 | 原有解决方案: 8 | BEM --- JS 9 | 10 | ```less 11 | @class-prefix-header: ~'header'; 12 | 13 | .@{class-prefix-header} { 14 | display: flex; 15 | justify-content: space-between; 16 | width: 100%; 17 | &-ul { 18 | display: flex; 19 | list-style: none; 20 | 21 | & > li { 22 | padding-right: 20px; 23 | } 24 | } 25 | 26 | &-logo { 27 | display: flex; 28 | align-items: center; 29 | width: auto; 30 | cursor: pointer; 31 | color: var(--color-text-1); 32 | } 33 | 34 | &-fullscreen { 35 | display: flex; 36 | align-items: center; 37 | cursor: pointer; 38 | } 39 | 40 | &-avatar { 41 | cursor: pointer; 42 | } 43 | } 44 | ``` 45 | 46 | 新的解决方案。webpack 配置css Module。同时兼容之前的处理方案, 配置如下 47 | 48 | ```js 49 | { 50 | test: /\.less$/i, 51 | use: [ 52 | { 53 | loader: 'style-loader', 54 | }, 55 | { 56 | loader: 'css-loader', 57 | options: { 58 | modules: { 59 | auto: (resourcePath) => resourcePath.endsWith('.module.less'), // 兼容之前的处理。只针对 .module.less 处理 60 | localIdentName: '[local]___[hash:base64:5]' 61 | } 62 | } 63 | }, 64 | { 65 | loader: 'less-loader', 66 | options: { 67 | lessOptions: { 68 | modifyVars: { 69 | 'arcoblue-6': '#3491FA', 70 | }, 71 | javascriptEnabled: true 72 | }, 73 | }, 74 | }, 75 | ], 76 | }, 77 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-arco-admin 2 | 3 | node 版本需要在v14之上 4 | 5 | ```bash 6 | pnpm i 7 | 8 | pnpm start 9 | 10 | ``` 11 | 12 | 13 | ## 该系统技术栈特点 14 | - 基于webpack5.x配置 15 | - eslint严格代码规范 16 | - prettier统一代码风格 17 | - husky代码提交检测 18 | - 环境变量配置规范化 19 | 20 | ## 技术框架 21 | - [x] react18 22 | - [x] react-dom 23 | - [x] react-router-dom 6.x 24 | - [x] react-hook-form 25 | - [x] hooks 26 | - [x] arco-design 27 | 28 | ## 代码规范 29 | - [x] eslint 30 | - [x] prettier 31 | - [x] husky 32 | 33 | 代码x层面需要对自己严格要去的规范 34 | 35 | 36 | ## 菜单结构 37 | 38 | 39 | 40 | ## 基础工程 TodoList 41 | 42 | - [x] 脚手架 43 | - [x] 国际化 44 | - [x] 主题配置 45 | - [x] CI/CD 46 | - [x] jenkins自动构建 47 | - [ ] 待补充 48 | - ... 49 | 50 | ## 页面 TodoList 51 | - [ ] 仪表盘 52 | - [x] 工作台 53 | - [ ] 实时监控 54 | - [ ] 数据可视化 55 | - [ ] 分析页 56 | - [ ] 多位数据分析 57 | - [ ] 列表页 58 | - [ ] 查询表格 59 | - [ ] 卡片列表 60 | - [ ] 表单页 61 | - [ ] 分组表单 62 | - [ ] 分布表单 63 | - [x] 结果页 64 | - [x] 成功页 65 | - [x] 失败页 66 | - [x] 异常页 67 | - [x] 403 68 | - [x] 404 69 | - [x] 500 70 | - [ ] 个人中心 71 | - [ ] 用户信息 72 | - [ ] 用户设置 73 | 74 | 75 | ## 支持打包产物上传至COS 76 | 77 | 在腾讯云网在购买COS服务,然后在项目根目录下创建.env文件,配置如下 78 | 79 | ```bash 80 | SecretId=xxxx 81 | SecretKey=xxxx 82 | Bucket=xxxx 83 | Region=xxx 84 | ``` 85 | 86 | 安装腾讯云cos-nodejs-sdk-v5 87 | 88 | ```bash 89 | pnpm add cos-nodejs-sdk-v5 90 | ``` 91 | 92 | 具体上传代码可参考 build/upload.js -------------------------------------------------------------------------------- /build/env.js: -------------------------------------------------------------------------------- 1 | const REACT_APP = /^REACT_APP_/i; // 防止过滤掉process.evn 下的一些环境变量 2 | function getClientEnvironment(publicUrl = '') { // PUBLIC_URL --> 公共URL 3 | const raw = Object.keys(process.env) 4 | .filter(key => REACT_APP.test(key)) 5 | .reduce( 6 | (env, key) => { 7 | env[key] = process.env[key]; 8 | return env; 9 | }, 10 | { 11 | NODE_ENV: process.env.NODE_ENV || 'development', 12 | PUBLIC_URL: publicUrl, 13 | } 14 | ); 15 | // 注入环境变量 16 | const stringified = { 17 | 'process.env': Object.keys(raw).reduce((env, key) => { 18 | env[key] = JSON.stringify(raw[key]); 19 | return env; 20 | }, {}), 21 | }; 22 | 23 | return { raw, stringified }; 24 | } 25 | 26 | module.exports = getClientEnvironment; 27 | -------------------------------------------------------------------------------- /build/fileList.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const async = require('async') 4 | const isWin = process.platform == 'win32' 5 | 6 | // 读取文件 7 | function fsReadDir(dir) { 8 | return new Promise((resolve, reject) => { 9 | fs.readdir(dir, (err, files) => { 10 | if (err) reject(err) 11 | resolve(files) 12 | }) 13 | }) 14 | } 15 | // 获取文件信息 16 | function fsStat(path) { 17 | return new Promise((resolve, reject) => { 18 | fs.stat(path, (err, stat) => { 19 | if (err) reject(err) 20 | resolve(stat) 21 | }) 22 | }) 23 | } 24 | 25 | /** 26 | * 获取文件夹列表 27 | * @param {*} folder 28 | * @returns 29 | */ 30 | async function getFolderList(folder) { 31 | return fsStat(folder.path).then((stat) => { 32 | if (stat.isDirectory()) { 33 | return fsReadDir(folder.path).then((list) => 34 | async 35 | .mapLimit(list, 16, (item, next) => { 36 | const f = { 37 | path: path.resolve(folder.path, item), 38 | name: path.join(folder.name, item), 39 | } 40 | getFolderList(f).then((res) => { 41 | next(null, res) 42 | }) 43 | }) 44 | .then((subFiles) => { 45 | const files = subFiles.reduce((prev, curr) => prev.concat(curr), []) 46 | return [...files, { ...folder, dir: true }] 47 | }), 48 | ) 49 | } 50 | const filePath = folder.path 51 | const fileName = isWin ? filePath.replace(/^.*(\/|\\)/, '') : filePath.replace(/^.*(\/)/, '') 52 | const info = { 53 | path: filePath, 54 | name: fileName, 55 | dir: false, 56 | } 57 | return [info] 58 | }) 59 | } 60 | 61 | module.exports = getFolderList 62 | -------------------------------------------------------------------------------- /build/upload.js: -------------------------------------------------------------------------------- 1 | const COS = require('cos-nodejs-sdk-v5') 2 | const dotenv = require('dotenv') 3 | const path = require('path') 4 | const fs = require('fs') 5 | const getFolderList = require('./fileList') 6 | const { name } = require('../package.json') 7 | dotenv.config() 8 | const { SecretId, SecretKey, Bucket, Region } = process.env 9 | 10 | const cos = new COS({ 11 | SecretId, 12 | SecretKey, 13 | }) 14 | const rootPath = path.resolve(__dirname, '../dist') 15 | 16 | async function uploadEvent() { 17 | const list = await getFolderList({ path: rootPath, name: 'dist' }) 18 | list.forEach((files) => { 19 | if (!files.dir) { 20 | fs.readFile(files.path, (err, data) => { 21 | if (err) throw err 22 | cos.putObject( 23 | { 24 | Bucket, 25 | Region, 26 | Key: files.path.replace(rootPath, `${name}/dist`), 27 | Body: data, // 上传文件对象 28 | onProgress: function (progressData) { 29 | console.log(JSON.stringify(progressData)) 30 | }, 31 | }, 32 | function (err, data) { 33 | console.log(err || data) 34 | }, 35 | ) 36 | }) 37 | } 38 | }) 39 | } 40 | uploadEvent() 41 | -------------------------------------------------------------------------------- /build/webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const CopyWebpackPlguin = require('copy-webpack-plugin') // 拷贝静态资源到public目录下 5 | const webpack = require('webpack') 6 | const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); 7 | const rootDir = path.resolve(__dirname, '..') 8 | const getClientEnvironment = require('./env') 9 | const env = getClientEnvironment() 10 | module.exports = { 11 | mode: 'none', 12 | target: 'web', 13 | entry: { 14 | index: path.join(rootDir, 'template/js/index.js'), 15 | app: path.resolve(rootDir, 'src/index.tsx') 16 | }, 17 | output: { 18 | filename: 'js/[name].[chunkhash:4].js', 19 | path: path.resolve(rootDir, 'dist'), 20 | // publicPath: 'https://oss.yaogeng.top/prod/web/reactArcoAdmin', 21 | publicPath: process.env.npm_lifecycle_event === 'deploy' ? 'https://code-magic-record.github.io/react-arco-admin/' : '/', 22 | clean: true, // 清空打包旧文件 23 | }, 24 | resolve: { 25 | alias: { 26 | '@': path.resolve(rootDir, 'src'), 27 | 'src': path.resolve(rootDir, 'src') 28 | }, 29 | extensions: ['.tsx', '.ts', '.js', '.jsx', 'css', 'less', '.json'], 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.(js|jsx|tsx?)$/, 35 | enforce: 'pre', 36 | use: [ 37 | { 38 | loader: 'eslint-loader', 39 | }, 40 | ], 41 | exclude: /node_modules/, 42 | }, 43 | { 44 | test: /\.(js|jsx|tsx?)$/, // es6->es5 45 | use: ['cache-loader', 'thread-loader', 'babel-loader?cacheDirectory=true'], // thread-loader 多线程打包 46 | include: path.resolve(rootDir, 'src'), 47 | exclude: /node_modules/, 48 | }, 49 | { 50 | test: /\.css$/i, 51 | use: [ 52 | 'cache-loader', 53 | 'style-loader', 54 | 'css-loader', 55 | { 56 | loader: 'postcss-loader', 57 | options: { 58 | postcssOptions: { 59 | plugins: ['autoprefixer'], 60 | }, 61 | }, 62 | }, 63 | ] 64 | }, 65 | { 66 | test: /\.less$/i, 67 | use: [ 68 | { 69 | loader: 'cache-loader', 70 | }, 71 | { 72 | loader: 'style-loader', 73 | }, 74 | { 75 | loader: 'css-loader', 76 | options: { 77 | modules: { 78 | auto: (resourcePath) => resourcePath.endsWith('.module.less'), 79 | localIdentName: '[local]___[hash:base64:5]' 80 | } 81 | } 82 | }, 83 | { 84 | loader: 'less-loader', 85 | options: { 86 | lessOptions: { 87 | modifyVars: { 88 | 'arcoblue-6': '#873bf4', 89 | }, 90 | javascriptEnabled: true 91 | }, 92 | }, 93 | }, 94 | ], 95 | }, 96 | { 97 | test: /\.(ico|png|jpe?g|gif|svg)$/, 98 | use: [ 99 | { 100 | loader: 'file-loader', 101 | options: { 102 | name: '[name].[ext]', 103 | outputPath: './image', 104 | limit: 1024 * 10, 105 | }, 106 | }, 107 | ], 108 | }, 109 | { 110 | test: /\.woff2$/, 111 | use: ['file-loader'], 112 | }, 113 | ], 114 | }, 115 | plugins: [ 116 | new HtmlWebpackPlugin({ 117 | filename: 'index.html', 118 | template: path.resolve(rootDir, 'template/index.html'), 119 | inject: 'body', 120 | scriptLoading: 'blocking', 121 | }), 122 | new CopyWebpackPlguin({ 123 | patterns: [ 124 | { 125 | from: '*.js', 126 | context: path.resolve(rootDir, 'template/js'), 127 | to: path.resolve(rootDir, 'dist/js/[name].js'), 128 | }, 129 | // { 130 | // from: '*.ico', 131 | // context: path.resolve(rootDir, 'template'), 132 | // to: path.resolve(rootDir, 'public'), 133 | // }, 134 | ], 135 | }), 136 | new webpack.DefinePlugin(env.stringified), // 配置环境变量 137 | new FriendlyErrorsWebpackPlugin({ 138 | compilationSuccessInfo: { 139 | // messages: [`You application is running here ${env.stringified['process.env'].REACT_APP_SERVER_DOMAIN}`], 140 | messages: ['You application is running here http://localhost:8080'], 141 | notes: ['successful 🚀'] 142 | }, 143 | }), 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /build/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const webpacBaseConfig = require('./webpack.config') 3 | const { merge } = require('webpack-merge') // 5.x 4 | 5 | module.exports = merge(webpacBaseConfig, { 6 | mode: process.env.NODE_ENV, 7 | devtool: 'eval-cheap-module-source-map', // 5.x 8 | cache: { 9 | type: 'memory', 10 | }, 11 | devServer: { 12 | host: '0.0.0.0', 13 | port: 8080, 14 | hot: true, 15 | stats: 'errors-only', 16 | quiet: true, 17 | useLocalIp: true, 18 | disableHostCheck: true, // Invalid Host header 解决打开自定义环境下报错的处理 19 | historyApiFallback: true, // 处理BowerRouter 20 | compress: true, 21 | proxy: { 22 | '/customerAdmin': { // 代理名字 23 | target: process.env.REACT_APP_TARGETURL, 24 | pathRewrite: { '^/customerAdmin': '' }, 25 | changeOrigin: true 26 | } 27 | }, 28 | }, 29 | plugins: [ 30 | new webpack.ProgressPlugin({}), 31 | ], 32 | }) 33 | -------------------------------------------------------------------------------- /build/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | const webpackBaseConfig = require('./webpack.config') 3 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const ArcoWebpackPlugin = require('@arco-design/webpack-plugin') 6 | const TerserPlugin = require('terser-webpack-plugin') 7 | const webpack = require('webpack') 8 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | mode: process.env.NODE_ENV, 12 | devtool: 'hidden-source-map', 13 | cache: { 14 | type: 'filesystem', 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.css$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | 'cache-loader', 23 | 'css-loader', 24 | { 25 | loader: 'postcss-loader', 26 | options: { 27 | postcssOptions: { 28 | plugins: ['autoprefixer'], 29 | }, 30 | }, 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | optimization: { 37 | splitChunks: { 38 | // 分割代码块 39 | cacheGroups: { 40 | // 缓存组 41 | common: { 42 | name: 'common', 43 | chunks: 'all', 44 | minSize: 0, 45 | minChunks: 1, // 用到两次以上 46 | }, 47 | vendor: { 48 | name: 'vendor', 49 | priority: 1, // 权重 50 | test: /node_modules/, 51 | chunks: 'all', 52 | minSize: 0, 53 | minChunks: 1, // 用到两次以上 54 | }, 55 | }, 56 | }, 57 | }, 58 | plugins: [ 59 | // new BundleAnalyzerPlugin(), 60 | new ArcoWebpackPlugin(), // Arco Ui的tree shaking 61 | new MiniCssExtractPlugin({ 62 | filename: 'css/[name].[chunkhash:4].css', 63 | chunkFilename: '[name].chunk.css', 64 | }), 65 | new CssMinimizerPlugin(), 66 | new webpack.BannerPlugin({ 67 | banner: 'yaogengzhu, Inc.\nAll rights reserved.\n', 68 | }), 69 | new TerserPlugin({ 70 | parallel: false, 71 | terserOptions: { 72 | nameCache: null, 73 | }, 74 | }), 75 | ], 76 | externals: { 77 | react: 'React', 78 | 'react-dom': 'ReactDOM', 79 | bizcharts: 'BizCharts', 80 | }, 81 | }) 82 | -------------------------------------------------------------------------------- /declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@arco-design/color' { 2 | export { generate, getRgbStr } from './node_modules/@arco-design/color/src/index'; 3 | } 4 | declare module 'react-dom/client' { 5 | export { createRoot } from 'react-dom/clien' 6 | } 7 | declare module '*.less' { 8 | const classes: { [className: string]: string }; 9 | export default classes; 10 | } 11 | 12 | declare module 'nprogress' { 13 | import nprogress from 'nprogress'; 14 | export default nprogress; 15 | } 16 | 17 | declare module '*.svg' { 18 | const content: React.FunctionComponent> | string; 19 | export default content; 20 | } 21 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 发生错误时终止 4 | set -e 5 | 6 | # 构建 7 | # npm run build 8 | 9 | # 进入构建文件夹 10 | cd dist 11 | 12 | # 如果你要部署到自定义域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | # git checkout -b master 17 | git add -A 18 | git commit -m 'deploy' 19 | 20 | # 如果你要部署在 https://.github.io 21 | # git push -f git@github.com:/.github.io.git main 22 | 23 | # 如果你要部署在 https://.github.io/ 24 | git push -f git@github.com:code-magic-record/react-arco-admin.git master:gh-pages 25 | 26 | cd - 27 | -------------------------------------------------------------------------------- /material/menus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-magic-record/react-arco-admin/f6734b23df25c251d4069153ad5f48617bdf012b/material/menus.png -------------------------------------------------------------------------------- /material/menus.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-magic-record/react-arco-admin/f6734b23df25c251d4069153ad5f48617bdf012b/material/menus.xmind -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-arco-admin", 3 | "version": "1.0.0", 4 | "description": "react、arco、admin", 5 | "main": "index.js", 6 | "author": "yaogengzhu", 7 | "license": "ISC", 8 | "scripts": { 9 | "prepare": "husky install", 10 | "start": "env-cmd -f .env.development node ./server/index", 11 | "build": "env-cmd -f .env.production webpack --config ./build/webpack.prod.js", 12 | "deploy": "env-cmd -f .env.production webpack --config ./build/webpack.prod.js & . deploy.sh", 13 | "deploy:cos": "node ./build/upload.js", 14 | "lint": "eslint --ext .ts --ext .tsx src --fix" 15 | }, 16 | "lint-staged": { 17 | "*.{js,jsx,tsx, ts}": [ 18 | "eslint --fix", 19 | "git add" 20 | ] 21 | }, 22 | "devDependencies": { 23 | "@arco-design/webpack-plugin": "^1.7.0", 24 | "@babel/core": "^7.14.6", 25 | "@babel/parser": "^7.18.0", 26 | "@babel/plugin-proposal-class-properties": "^7.14.5", 27 | "@babel/plugin-proposal-decorators": "^7.15.8", 28 | "@babel/plugin-proposal-private-methods": "^7.14.5", 29 | "@babel/plugin-transform-runtime": "^7.14.5", 30 | "@babel/preset-env": "^7.14.7", 31 | "@babel/preset-react": "^7.14.5", 32 | "@commitlint/cli": "^16.2.1", 33 | "@commitlint/config-conventional": "^16.2.1", 34 | "@types/react": "^17.0.27", 35 | "@types/react-color": "^3.0.6", 36 | "@types/react-dom": "^17.0.17", 37 | "@typescript-eslint/eslint-plugin": "^5.7.0", 38 | "autoprefixer": "^10.3.0", 39 | "babel-eslint": "^10.1.0", 40 | "babel-loader": "^8.2.2", 41 | "copy-webpack-plugin": "^9.0.1", 42 | "css-minimizer-webpack-plugin": "^3.0.2", 43 | "eslint": "^7.32.0", 44 | "eslint-config-prettier": "^8.3.0", 45 | "eslint-plugin-import": "^2.23.4", 46 | "eslint-plugin-prettier": "^3.4.0", 47 | "eslint-plugin-react": "^7.24.0", 48 | "husky": "^8.0.1", 49 | "lint-staged": "^11.1.2", 50 | "npm": "^8.0.0", 51 | "optimize-css-assets-webpack-plugin": "^6.0.1", 52 | "postcss": "^8.3.5", 53 | "postcss-loader": "^6.1.1", 54 | "pre-commit": "^1.2.2", 55 | "prettier": "^2.3.2", 56 | "thread-loader": "^3.0.4", 57 | "ts-loader": "^9.2.6", 58 | "typescript": "^4.5.5", 59 | "webpack-cli": "^4.7.2" 60 | }, 61 | "dependencies": { 62 | "@arco-design/color": "^0.4.0", 63 | "@arco-design/web-react": "^2.33.0", 64 | "@babel/preset-typescript": "^7.15.0", 65 | "@react-spring/web": "^9.4.5", 66 | "@types/mockjs": "^1.0.6", 67 | "@types/uuid": "^8.3.4", 68 | "@use-gesture/react": "^10.2.12", 69 | "ahooks": "^3.1.9", 70 | "async": "^3.2.4", 71 | "axios": "^0.26.0", 72 | "babel-plugin-import": "^1.13.3", 73 | "bizcharts": "^4.1.16", 74 | "browserslist": "^4.16.6", 75 | "cache-loader": "^4.1.0", 76 | "chalk": "^4.1.2", 77 | "classnames": "^2.3.1", 78 | "cos-nodejs-sdk-v5": "^2.11.15", 79 | "css-loader": "^5.2.6", 80 | "dotenv": "^16.0.3", 81 | "env-cmd": "^10.1.0", 82 | "eslint-loader": "^4.0.2", 83 | "file-loader": "^6.2.0", 84 | "friendly-errors-webpack-plugin": "^1.7.0", 85 | "history": "^5.2.0", 86 | "html-webpack-plugin": "^5.3.2", 87 | "ip": "^1.1.5", 88 | "less": "^4.1.1", 89 | "less-loader": "^10.0.1", 90 | "mini-css-extract-plugin": "^2.1.0", 91 | "mockjs": "^1.1.0", 92 | "nprogress": "^0.2.0", 93 | "prop-types": "^15.7.2", 94 | "query-string": "^7.1.1", 95 | "react": "^18.1.0", 96 | "react-color": "^2.19.3", 97 | "react-dom": "^18.1.0", 98 | "react-error-boundary": "^3.1.4", 99 | "react-router-dom": "^6.2.1", 100 | "react-spring": "^9.4.5", 101 | "style-loader": "^3.0.0", 102 | "terser-webpack-plugin": "^5.3.3", 103 | "url-loader": "^4.1.1", 104 | "uuid": "^8.3.2", 105 | "webpack": "^5.80.0", 106 | "webpack-bundle-analyzer": "^4.6.1", 107 | "webpack-dev-server": "^3.11.2", 108 | "webpack-merge": "^5.8.0" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 百安居移动端脚手架
-------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const Webpack = require('webpack') 2 | const WebpackDevServer = require('webpack-dev-server') 3 | // webpack开发 配置文件 4 | const webpackConfig = require('../build/webpack.dev') 5 | // 自定义日志输出 6 | const logger = require('./logger') 7 | // 服务配置 8 | const { port, host } = webpackConfig.devServer // 监听的端口号 9 | // 编译器 10 | const compiler = Webpack(webpackConfig) 11 | // devServer 参数 12 | const devServerOptions = Object.assign({}, webpackConfig.devServer) // devServer配置 13 | const server = new WebpackDevServer(compiler, devServerOptions) 14 | 15 | server.listen(port, host, async (err) => { 16 | if (err) { 17 | return logger.error(err.message) 18 | } 19 | logger.appStarted(port, host) 20 | }) 21 | -------------------------------------------------------------------------------- /server/logger.js: -------------------------------------------------------------------------------- 1 | const ip = require('ip') 2 | const chalk = require('chalk') 3 | const divider = chalk.gray('\n-----------------------------------') 4 | const logger = { 5 | error: (err) => { 6 | console.error(chalk.red(err)) 7 | }, 8 | appStarted: (port, host, tunnelStarted) => { 9 | console.log(`Server started ! ${chalk.green('✓')}`) 10 | if (tunnelStarted) { 11 | console.log(`Tunnel initialised ${chalk.green('✓')}`) 12 | } 13 | console.log(` 14 | ${chalk.bold('Access URLs:')}${divider} 15 | Localhost: ${chalk.magenta(`${process.env.REACT_APP_SERVER_DOMAIN}:${port}`)} 16 | 本地IP地址: ${chalk.magenta(`http://${ip.address()}:${port}`) + 17 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')} 18 | ${divider} 19 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)} 20 | `) 21 | }, 22 | } 23 | 24 | module.exports = logger 25 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useMemo, useState } from 'react'; 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | import { ConfigProvider } from '@arco-design/web-react'; 4 | import enUS from '@arco-design/web-react/es/locale/en-US'; 5 | import zhCN from '@arco-design/web-react/es/locale/zh-CN'; 6 | import '@arco-design/web-react/dist/css/arco.css'; 7 | import { useColor, useTheme } from 'src/ahooks'; 8 | import { useLocalStorageState } from 'ahooks'; 9 | import { Login } from './app/Login'; 10 | import { Home } from './app/Home'; 11 | import Loading from './components/Loading/Loading'; 12 | import { GlobalContext, ILang } from './utils/GlobalContext'; 13 | import './index.less'; 14 | 15 | const App = () => { 16 | useTheme(); 17 | useColor(); 18 | const [language] = useLocalStorageState('language'); 19 | const [lang, setLang] = useState(language ?? 'zh-CN'); 20 | const contextValue = { 21 | lang, 22 | setLang, 23 | }; 24 | 25 | const locale = useMemo(() => { 26 | switch (lang) { 27 | case 'zh-CN': 28 | return zhCN; 29 | case 'en-US': 30 | return enUS; 31 | default: 32 | return zhCN; 33 | } 34 | }, [lang]); 35 | 36 | return ( 37 | 38 | 39 | 40 | }> 41 | 42 | } /> 43 | } /> 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | }; 51 | export default App; 52 | -------------------------------------------------------------------------------- /src/Layout/Bread/Bread.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { Breadcrumb } from '@arco-design/web-react'; 3 | import { IconHome } from '@arco-design/web-react/icon'; 4 | const BreadcrumbItem = Breadcrumb.Item; 5 | 6 | const Bread = () => { 7 | useEffect(() => { 8 | getBreadListByHostName() 9 | }, []) 10 | 11 | const getBreadListByHostName = () => { 12 | // const hostname = window.location.hostname 13 | // console.log(hostname, '??') 14 | } 15 | return ( 16 |
17 | 18 | 19 | 20 | 21 | Channel 22 | News 23 | 24 |
25 | ) 26 | } 27 | 28 | export default Bread 29 | -------------------------------------------------------------------------------- /src/Layout/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { Button, Dropdown, Menu, Tooltip, Message, Avatar } from '@arco-design/web-react'; 4 | import { 5 | IconFullscreen, 6 | IconFullscreenExit, 7 | IconMoonFill, 8 | IconPoweroff, 9 | IconSettings, 10 | IconSunFill, 11 | IconLanguage, 12 | } from '@arco-design/web-react/icon'; 13 | import { useFullscreen, useLocalStorageState } from 'ahooks'; 14 | import { useTheme } from 'src/ahooks'; 15 | import PageConfig from 'src/components/PageConifg/PageConfig'; 16 | import useI18n from 'src/ahooks/useI18n'; 17 | import styles from './index.module.less'; 18 | import githubSvg from 'src/assets/images/github.svg' 19 | 20 | const themeStyle = { 21 | background: 'var(--theme-color)', 22 | color: '#fff', 23 | }; 24 | const Header = () => { 25 | useTheme(); 26 | const navigate = useNavigate(); 27 | const { lang, i18n, setLang } = useI18n(); 28 | const [, setLanguage] = useLocalStorageState('language'); 29 | const [arcoThem, setArcoThem] = useLocalStorageState('arco-theme'); 30 | const [them, setThem] = useState(''); 31 | const [fullscreen, { toggleFullscreen }] = useFullscreen(() => document.documentElement); 32 | const loginOut = () => { 33 | localStorage.removeItem('userToken'); 34 | navigate('/login'); 35 | }; 36 | 37 | useEffect(() => { 38 | if (arcoThem) { 39 | setThem('dark'); 40 | } else { 41 | setThem(''); 42 | } 43 | }, []); 44 | 45 | const goHome = () => { 46 | navigate('/weclome'); 47 | }; 48 | 49 | const fullscreenEvent = () => { 50 | toggleFullscreen(); 51 | }; 52 | 53 | const changeLanguage = (lang: string) => { 54 | if (lang === 'zh-CN') { 55 | Message.info('语言切换至 zh-CN'); 56 | setLang('zh-CN'); 57 | setLanguage('zh-CN'); 58 | } else { 59 | Message.info('Language switch to en-US'); 60 | setLang('en-US'); 61 | setLanguage('en-US'); 62 | } 63 | }; 64 | 65 | const languageList = ( 66 | 67 | 68 | 中文 69 | 70 | 71 | English 72 | 73 | 74 | ); 75 | 76 | return ( 77 |
78 |
79 |

React Arco Admin

80 |
81 |
    82 |
  • 83 | 84 | 87 | 88 |
  • 89 |
  • 90 | {them === 'dark' ? ( 91 | 92 | 103 | 104 | ) : ( 105 | 106 | 117 | 118 | )} 119 |
  • 120 |
  • 121 | 122 | 125 | 126 |
  • 127 |
  • 128 | {!fullscreen ? ( 129 | 130 | 133 | 134 | ) : ( 135 | 136 | 139 | 140 | )} 141 |
  • 142 |
  • 143 | 150 |
  • 151 |
  • 152 | 156 | 157 | 158 | {i18n[lang]['header.userSetting']} 159 | 160 | 161 | 162 | {i18n[lang]['header.logout']} 163 | 164 | 165 | } 166 | > 167 | 168 | avatar 169 | 170 | 171 |
  • 172 |
173 |
174 | ); 175 | }; 176 | 177 | export default Header; 178 | -------------------------------------------------------------------------------- /src/Layout/Header/index.module.less: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | justify-content: space-between; 4 | width: 100%; 5 | } 6 | 7 | .logo { 8 | display: flex; 9 | align-items: center; 10 | width: auto; 11 | cursor: pointer; 12 | color: var(--color-text-1); 13 | } 14 | 15 | .ul { 16 | display: flex; 17 | list-style: none; 18 | 19 | & > li { 20 | padding-right: 20px; 21 | } 22 | } 23 | 24 | .fullscreen { 25 | display: flex; 26 | align-items: center; 27 | cursor: pointer; 28 | } 29 | 30 | .avatar { 31 | cursor: pointer; 32 | } 33 | 34 | .themeStyle { 35 | background: var(--theme-color); 36 | color: #fff; 37 | } 38 | -------------------------------------------------------------------------------- /src/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from '@arco-design/web-react'; 2 | import { IconCaretRight, IconCaretLeft } from '@arco-design/web-react/icon'; 3 | import React, { useState } from 'react'; 4 | import Header from './Header/Header'; 5 | import './index.less'; 6 | import MainRoute from './MainRoute'; 7 | import { MenuComponent } from './Menu'; 8 | 9 | const Sider = Layout.Sider; 10 | const Footer = Layout.Footer; 11 | const Content = Layout.Content; 12 | 13 | const LayoutMain: React.FC = () => { 14 | const [collapsed, setCollapsed] = useState(false); 15 | 16 | const handleCollapsed = () => { 17 | setCollapsed(!collapsed); 18 | }; 19 | return ( 20 | 21 |
22 |
23 |
24 | : } 30 | breakpoint="xl" 31 | > 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
鄂ICP备18026800号-1
40 |
41 |
42 |
43 | ); 44 | }; 45 | export default LayoutMain; 46 | -------------------------------------------------------------------------------- /src/Layout/MainRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useEffect } from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import Loading from 'src/components/Loading/Loading'; 4 | import RouteConfig, { IRouterConfig } from '../conifg/routerConfig'; 5 | import nprogress from 'nprogress'; 6 | import '@/assets/css/nprogress.less'; 7 | import Welcome from 'src/app/Welcome' 8 | 9 | const LazyLoad = () => { 10 | useEffect(() => { 11 | nprogress.configure({ showSpinner: false }); 12 | nprogress.start(); 13 | return () => { 14 | nprogress.done(); 15 | }; 16 | }, []); 17 | 18 | return ; 19 | }; 20 | 21 | const getRouter = () => { 22 | return RouteConfig.map((item: IRouterConfig) => { 23 | return ; 24 | }); 25 | }; 26 | 27 | const MainRoute = () => { 28 | return ( 29 | }> 30 | 31 | } /> 32 | {getRouter()} 33 | 34 | 35 | ); 36 | }; 37 | export default MainRoute; 38 | -------------------------------------------------------------------------------- /src/Layout/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Menu } from '@arco-design/web-react'; 3 | import { 4 | IconHome, 5 | IconCalendar, 6 | IconDashboard, 7 | IconDice, 8 | IconApps, 9 | IconList, 10 | IconFile, 11 | IconCheckCircle, 12 | IconUser, 13 | IconExclamationCircle, 14 | } from '@arco-design/web-react/icon'; 15 | import { IMenusItem, menuConfig } from '../conifg/menuConfig'; 16 | import { useLocation, Link } from 'react-router-dom'; 17 | import useI18n from 'src/ahooks/useI18n'; 18 | const MenuItem = Menu.Item; 19 | const SubMenu = Menu.SubMenu; 20 | 21 | interface IconsPros { 22 | [key: string]: React.ReactElement; 23 | } 24 | const icons: IconsPros = { 25 | IconHome: , 26 | IconCalendar: , 27 | IconDashboard: , 28 | IconDice: , 29 | IconApps: , 30 | IconList: , 31 | IconFile: , 32 | IconCheckCircle: , 33 | IconUser: , 34 | IconExclamationCircle: , 35 | }; 36 | const menu: IMenusItem[] = menuConfig.menu; 37 | 38 | const getMenu = (menus: IMenusItem[]) => { 39 | const { lang, i18n } = useI18n(); 40 | const list = menus.map((item) => { 41 | if (item.children && item.children.length > 0) { 42 | return ( 43 | 47 | {item.icon && icons[item.icon]} 48 | {i18n[lang][item.name]} 49 | 50 | } 51 | key={item.key} 52 | > 53 | {getMenu(item.children)} 54 | 55 | ); 56 | } 57 | return 58 | 59 | {i18n[lang][item.name]} 60 | 61 | ; 62 | }); 63 | return list; 64 | }; 65 | 66 | export const MenuComponent = () => { 67 | const [selectedKey, setSelectedKey] = useState([]); 68 | const [openKeys, setOpenKeys] = useState([]); 69 | const location = useLocation(); 70 | useEffect(() => { 71 | console.log(location.pathname) 72 | initMenus(); 73 | }, [location.pathname]); 74 | 75 | function initMenus() { 76 | const key = location.pathname.split('/') ? '/' + location.pathname.split('/')[1] : '' 77 | setOpenKeys([key]); 78 | setSelectedKey([location.pathname]); 79 | } 80 | const onClickMenuItem = (key: string) => { 81 | setSelectedKey([key]); 82 | }; 83 | 84 | return ( 85 | { 90 | setOpenKeys(openKeys); 91 | }} 92 | openKeys={openKeys} 93 | > 94 | {getMenu(menu)} 95 | 96 | ); 97 | }; 98 | -------------------------------------------------------------------------------- /src/Layout/index.less: -------------------------------------------------------------------------------- 1 | .layout-collapse { 2 | height: 100vh; 3 | border: 1px solid var(--color-border); 4 | overflow: hidden; 5 | } 6 | 7 | .layout-collapse .layout-main { 8 | background: var(--color-bg-1); 9 | min-width: 1100px; 10 | } 11 | 12 | .layout-collapse .header { 13 | width: 100%; 14 | height: 60px; 15 | position: fixed; 16 | top: 0; 17 | z-index: 2; 18 | background: var(--color-bg-2); 19 | border-bottom: 1px solid var(--color-border); 20 | box-sizing: border-box; 21 | } 22 | 23 | .layout-collapse .layout-content { 24 | background: var(--color-fill-2); 25 | } 26 | 27 | .layout-collapse .arco-layout-content { 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | background: var(--color-bg-1); 32 | font-stretch: condensed; 33 | font-size: 16px; 34 | } 35 | 36 | .layout-collapse .arco-layout-footer { 37 | color: var(--color-text-2); 38 | text-align: center; 39 | height: 48px; 40 | line-height: 48px; 41 | font-weight: 400; 42 | font-size: 14px; 43 | } 44 | 45 | // .layout-collapse .arco-layout-content { 46 | // color: var(--color-text-2); 47 | // font-weight: 400; 48 | // font-size: 14px; 49 | // } 50 | 51 | .layout-collapse .arco-layout-header .trigger { 52 | margin-left: 20px; 53 | } 54 | -------------------------------------------------------------------------------- /src/ahooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useLocalStorageState } from 'ahooks'; 2 | import { useEffect } from 'react'; 3 | import { changePageColor } from 'src/utils' 4 | 5 | export const useTheme = () => { 6 | const [arcoThem] = useLocalStorageState('arco-theme'); 7 | useEffect(() => { 8 | if (arcoThem) { 9 | document.body.setAttribute('arco-theme', 'dark'); 10 | } else { 11 | document.body.setAttribute('arco-theme', ''); 12 | } 13 | }, []); 14 | }; 15 | 16 | export const useColor = () => { 17 | const [arcoThemColor, setArcoThemColor] = useLocalStorageState('arco-theme-color'); 18 | useEffect(() => { 19 | if (!arcoThemColor) { 20 | setArcoThemColor('#873bf4') 21 | } 22 | 23 | changePageColor(arcoThemColor || '#873bf4') 24 | }, [arcoThemColor]); 25 | return [arcoThemColor, setArcoThemColor]; 26 | }; 27 | -------------------------------------------------------------------------------- /src/ahooks/useI18n.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import i18n, { II18n } from 'src/locales' 3 | import { GlobalContext } from 'src/utils/GlobalContext'; 4 | 5 | function useI18n(locale: II18n = i18n) { 6 | const { lang, setLang } = useContext(GlobalContext); 7 | return { 8 | lang, 9 | i18n: locale, 10 | setLang, 11 | }; 12 | } 13 | 14 | export default useI18n; 15 | -------------------------------------------------------------------------------- /src/app/Dashboard/Monitor/Monitor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ErrorBoundary } from 'react-error-boundary' 3 | 4 | const Monitor = () => { 5 | const [msg, setMsg] = useState('这是一个正常的组件') 6 | return ( 7 |
8 | 组件内部错误
}> 9 |
{ 10 | setMsg({a: 1}) 11 | }}>{msg}
12 | 13 |
其它页面信息xxss11
14 | 15 | ); 16 | }; 17 | 18 | export default Monitor; 19 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/WorkPlace.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | import styles from './index.module.less'; 4 | import Banner from './modules/slide/banner'; 5 | import ContentView from './modules/slide/contentView' 6 | import Overview from './modules/slide/overview'; 7 | import ShortCuts from './modules/slide/shortcuts'; 8 | 9 | const { Row, Col } = Grid; 10 | 11 | const Workplace = () => { 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | ); 26 | }; 27 | 28 | export default Workplace; 29 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/index.module.less: -------------------------------------------------------------------------------- 1 | .workplace { 2 | box-sizing: border-box; 3 | width: 100%; 4 | height: 100%; 5 | background-color: var(--color-fill-2); 6 | } 7 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/mock/index.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | // import qs from 'query-string'; 3 | import setupMock from 'src/utils/setupMock'; 4 | 5 | setupMock({ 6 | setup: () => { 7 | Mock.mock(new RegExp('/api/workplace/overview-content'), () => { 8 | const year = new Date().getFullYear(); 9 | const getLineData = () => { 10 | return new Array(12).fill(0) 11 | .map((_item, index) => ({ 12 | date: `${year}-${index + 1}`, 13 | count: Mock.Random.natural(20000, 75000), 14 | })); 15 | }; 16 | return { 17 | allContents: '373.5w+', 18 | liveContents: '368', 19 | increaseComments: '8874', 20 | growthRate: '2.8%', 21 | chartData: getLineData(), 22 | }; 23 | }); 24 | 25 | const getList = () => { 26 | const { list } = Mock.mock({ 27 | 'list|100': [ 28 | { 29 | 'rank|+1': 1, 30 | title: () => 31 | Mock.Random.pick([ 32 | '经济日报:财政政策要精准提升效能', 33 | '“双12”遇冷消费者厌倦了电商平台的促销“套路”', 34 | '致敬坚守战“疫”一线的社区工作者', 35 | '普高还是职高?家长们陷入选校难题', 36 | ]), 37 | pv: function () { 38 | return 500000 - 3200; 39 | }, 40 | increase: '@float(-1, 1)', 41 | }, 42 | ], 43 | }); 44 | return list; 45 | }; 46 | const listText = getList(); 47 | const listPic = getList(); 48 | const listVideo = getList(); 49 | 50 | Mock.mock(new RegExp('/api/workplace/popular-contents'), () => { 51 | const list = [listText, listPic, listVideo][Number()]; 52 | return { 53 | list: list.slice(1, 6), 54 | total: 100, 55 | }; 56 | }); 57 | 58 | Mock.mock(new RegExp('/api/workplace/content-percentage'), () => { 59 | return [ 60 | { 61 | type: '纯文本', 62 | count: 148564, 63 | percent: 0.16, 64 | }, 65 | { 66 | type: '图文类', 67 | count: 334271, 68 | percent: 0.36, 69 | }, 70 | { 71 | type: '视频类', 72 | count: 445695, 73 | percent: 0.48, 74 | }, 75 | ]; 76 | }); 77 | 78 | Mock.mock(new RegExp('/api/workplace/announcement'), () => { 79 | return [ 80 | { 81 | type: 'activity', 82 | key: '1', 83 | content: '内容最新优惠活动', 84 | }, 85 | { 86 | type: 'info', 87 | key: '2', 88 | content: '新增内容尚未通过审核,详情请点击查看。', 89 | }, 90 | { 91 | type: 'notice', 92 | key: '3', 93 | content: '当前产品试用期即将结束,如需续费请点击查看。', 94 | }, 95 | { 96 | type: 'notice', 97 | key: '4', 98 | content: '1 月新系统升级计划通知', 99 | }, 100 | { 101 | type: 'info', 102 | key: '5', 103 | content: '新增内容已经通过审核,详情请点击查看。', 104 | }, 105 | ]; 106 | }); 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/chart/conentChart.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DonutChart } from 'bizcharts'; 3 | import { haderList } from '../config'; 4 | 5 | const ConentChart = () => { 6 | return ( 7 |
8 | 18 |
19 | ); 20 | }; 21 | 22 | export default ConentChart; 23 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/config/index.ts: -------------------------------------------------------------------------------- 1 | type IhaderList = Array<{ 2 | id: number; 3 | title: string; 4 | }>; 5 | export const haderList: IhaderList = [ 6 | { 7 | id: 1, 8 | title: '文本', 9 | }, 10 | { 11 | id: 2, 12 | title: '图文', 13 | }, 14 | { 15 | id: 3, 16 | title: '视频', 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/ContentData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart, LineAdvance } from 'bizcharts'; 3 | 4 | const data = [ 5 | { 6 | month: 'Jan', 7 | city: 'Tokyo', 8 | temperature: 7, 9 | }, 10 | { 11 | month: 'Jan', 12 | city: 'London', 13 | temperature: 3.9, 14 | }, 15 | { 16 | month: 'Feb', 17 | city: 'Tokyo', 18 | temperature: 13, 19 | }, 20 | { 21 | month: 'Feb', 22 | city: 'London', 23 | temperature: 4.2, 24 | }, 25 | { 26 | month: 'Mar', 27 | city: 'Tokyo', 28 | temperature: 16.5, 29 | }, 30 | { 31 | month: 'Mar', 32 | city: 'London', 33 | temperature: 5.7, 34 | }, 35 | { 36 | month: 'Apr', 37 | city: 'Tokyo', 38 | temperature: 14.5, 39 | }, 40 | { 41 | month: 'Apr', 42 | city: 'London', 43 | temperature: 8.5, 44 | }, 45 | { 46 | month: 'May', 47 | city: 'Tokyo', 48 | temperature: 10, 49 | }, 50 | { 51 | month: 'May', 52 | city: 'London', 53 | temperature: 11.9, 54 | }, 55 | { 56 | month: 'Jun', 57 | city: 'Tokyo', 58 | temperature: 7.5, 59 | }, 60 | { 61 | month: 'Jun', 62 | city: 'London', 63 | temperature: 15.2, 64 | }, 65 | { 66 | month: 'Jul', 67 | city: 'Tokyo', 68 | temperature: 9.2, 69 | }, 70 | { 71 | month: 'Jul', 72 | city: 'London', 73 | temperature: 17, 74 | }, 75 | { 76 | month: 'Aug', 77 | city: 'Tokyo', 78 | temperature: 14.5, 79 | }, 80 | { 81 | month: 'Aug', 82 | city: 'London', 83 | temperature: 16.6, 84 | }, 85 | { 86 | month: 'Sep', 87 | city: 'Tokyo', 88 | temperature: 9.3, 89 | }, 90 | { 91 | month: 'Sep', 92 | city: 'London', 93 | temperature: 14.2, 94 | }, 95 | { 96 | month: 'Oct', 97 | city: 'Tokyo', 98 | temperature: 8.3, 99 | }, 100 | { 101 | month: 'Oct', 102 | city: 'London', 103 | temperature: 10.3, 104 | }, 105 | { 106 | month: 'Nov', 107 | city: 'Tokyo', 108 | temperature: 8.9, 109 | }, 110 | { 111 | month: 'Nov', 112 | city: 'London', 113 | temperature: 5.6, 114 | }, 115 | { 116 | month: 'Dec', 117 | city: 'Tokyo', 118 | temperature: 5.6, 119 | }, 120 | { 121 | month: 'Dec', 122 | city: 'London', 123 | temperature: 9.8, 124 | }, 125 | ]; 126 | 127 | const ContentData = () => { 128 | return ( 129 |
130 | 131 | 132 | 133 |
134 | ); 135 | }; 136 | 137 | export default ContentData; 138 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/Shortcuts.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Divider, Link, Typography } from '@arco-design/web-react'; 2 | import { IconFile, IconFire, IconMobile, IconSettings, IconStorage } from '@arco-design/web-react/icon'; 3 | import React from 'react'; 4 | import './style/shortcuts.less'; 5 | 6 | const classPrefix = 'shortcuts'; 7 | const ShortCuts = () => { 8 | const shortcutList = [ 9 | { 10 | title: '内容管理', 11 | key: 'Content Management', 12 | icon: , 13 | }, 14 | { 15 | title: '内容数据', 16 | key: 'Content Statistic', 17 | icon: , 18 | }, 19 | { 20 | title: '高级管理', 21 | key: 'Advanced Management', 22 | icon: , 23 | }, 24 | { 25 | title: '线上推广', 26 | key: 'Online Promotion', 27 | icon: , 28 | }, 29 | { 30 | title: '内容投放', 31 | key: 'Marketing', 32 | icon: , 33 | }, 34 | ]; 35 | 36 | const recentShortcuts = [ 37 | { 38 | title: '内容数据', 39 | key: 'Content Statistic', 40 | icon: , 41 | }, 42 | { 43 | title: '内容管理', 44 | key: 'Content Management', 45 | icon: , 46 | }, 47 | { 48 | title: '高级管理', 49 | key: 'Advanced Management', 50 | icon: , 51 | }, 52 | ]; 53 | 54 | return ( 55 | 56 |
57 | 快捷入口 58 | 查看 59 |
60 | 61 |
62 | {shortcutList.map((item) => ( 63 |
64 |
{item.icon}
65 |
{item.title}
66 |
67 | ))} 68 |
69 | 70 |
71 | 最近访问 72 |
73 |
74 | {recentShortcuts.map((item) => ( 75 |
76 |
{item.icon}
77 |
{item.title}
78 |
79 | ))} 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default ShortCuts; 86 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/banner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Carousel } from '@arco-design/web-react'; 3 | 4 | const imageSrc = [ 5 | '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp', 6 | '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp', 7 | '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp', 8 | '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp', 9 | ]; 10 | function Banner() { 11 | return ( 12 | 22 | {imageSrc.map((src, index) => ( 23 |
24 | 31 |
32 | ))} 33 |
34 | ); 35 | } 36 | 37 | export default Banner; 38 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/contentData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart, LineAdvance } from 'bizcharts'; 3 | 4 | const data = [ 5 | { 6 | month: 'Jan', 7 | city: 'Tokyo', 8 | temperature: 7, 9 | }, 10 | { 11 | month: 'Jan', 12 | city: 'London', 13 | temperature: 3.9, 14 | }, 15 | { 16 | month: 'Feb', 17 | city: 'Tokyo', 18 | temperature: 13, 19 | }, 20 | { 21 | month: 'Feb', 22 | city: 'London', 23 | temperature: 4.2, 24 | }, 25 | { 26 | month: 'Mar', 27 | city: 'Tokyo', 28 | temperature: 16.5, 29 | }, 30 | { 31 | month: 'Mar', 32 | city: 'London', 33 | temperature: 5.7, 34 | }, 35 | { 36 | month: 'Apr', 37 | city: 'Tokyo', 38 | temperature: 14.5, 39 | }, 40 | { 41 | month: 'Apr', 42 | city: 'London', 43 | temperature: 8.5, 44 | }, 45 | { 46 | month: 'May', 47 | city: 'Tokyo', 48 | temperature: 10, 49 | }, 50 | { 51 | month: 'May', 52 | city: 'London', 53 | temperature: 11.9, 54 | }, 55 | { 56 | month: 'Jun', 57 | city: 'Tokyo', 58 | temperature: 7.5, 59 | }, 60 | { 61 | month: 'Jun', 62 | city: 'London', 63 | temperature: 15.2, 64 | }, 65 | { 66 | month: 'Jul', 67 | city: 'Tokyo', 68 | temperature: 9.2, 69 | }, 70 | { 71 | month: 'Jul', 72 | city: 'London', 73 | temperature: 17, 74 | }, 75 | { 76 | month: 'Aug', 77 | city: 'Tokyo', 78 | temperature: 14.5, 79 | }, 80 | { 81 | month: 'Aug', 82 | city: 'London', 83 | temperature: 16.6, 84 | }, 85 | { 86 | month: 'Sep', 87 | city: 'Tokyo', 88 | temperature: 9.3, 89 | }, 90 | { 91 | month: 'Sep', 92 | city: 'London', 93 | temperature: 14.2, 94 | }, 95 | { 96 | month: 'Oct', 97 | city: 'Tokyo', 98 | temperature: 8.3, 99 | }, 100 | { 101 | month: 'Oct', 102 | city: 'London', 103 | temperature: 10.3, 104 | }, 105 | { 106 | month: 'Nov', 107 | city: 'Tokyo', 108 | temperature: 8.9, 109 | }, 110 | { 111 | month: 'Nov', 112 | city: 'London', 113 | temperature: 5.6, 114 | }, 115 | { 116 | month: 'Dec', 117 | city: 'Tokyo', 118 | temperature: 5.6, 119 | }, 120 | { 121 | month: 'Dec', 122 | city: 'London', 123 | temperature: 9.8, 124 | }, 125 | ]; 126 | 127 | const ContentData = () => { 128 | return ( 129 |
130 | 131 | 132 | 133 |
134 | ); 135 | }; 136 | 137 | export default ContentData; 138 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/contentView.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Grid, Link, Radio, Table } from '@arco-design/web-react' 2 | import { useRequest } from 'ahooks' 3 | import axios from 'axios' 4 | import React, { useState } from 'react' 5 | import ConentChart from '../chart/conentChart' 6 | import { haderList } from '../config' 7 | import '../../mock/index' 8 | import '../../mock' 9 | import styles from './style/content.module.less' 10 | 11 | const { Row, Col } = Grid 12 | 13 | const Index = () => { 14 | const [type, setType] = useState(1) 15 | const [page, setPage] = useState(1) 16 | const [total, setTotal] = useState(0) 17 | const [data, setData] = useState>([]) 18 | 19 | const getList = ({ page = 0 }) => { 20 | return axios.get(`/api/workplace/popular-contents?page=${page}&pageSize=5&category=${type}`) 21 | } 22 | 23 | const { loading } = useRequest((params) => getList(params), { 24 | refreshDeps: [page, type], 25 | defaultParams: [ 26 | { 27 | page: page, 28 | }, 29 | ], 30 | onSuccess: (res) => { 31 | const { 32 | data: { list, total }, 33 | } = res 34 | setTotal(total) 35 | if (Array.isArray(list)) { 36 | setData([...list]) 37 | } 38 | }, 39 | }) 40 | 41 | const columns = [ 42 | { 43 | title: '排名', 44 | dataIndex: 'rank', 45 | width: 65, 46 | }, 47 | { 48 | title: '点击量', 49 | dataIndex: 'pv', 50 | width: 100, 51 | render: (text: number) => { 52 | return `${text / 1000}k` 53 | }, 54 | }, 55 | { 56 | title: '日涨幅', 57 | dataIndex: 'increase', 58 | sorter: (a: { increase: number }, b: { increase: number }) => a.increase - b.increase, 59 | width: 110, 60 | }, 61 | ] 62 | 63 | return ( 64 | 65 | 66 | 查看更多}> 67 | 68 | {haderList.map((item) => ( 69 | 70 | {item.title} 71 | 72 | ))} 73 | 74 | { 82 | setPage(pagination.current) 83 | }} 84 | pagination={{ total, current: page, pageSize: 5, simple: true }} 85 | /> 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ) 95 | } 96 | 97 | export default Index 98 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/overview.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { Card, Divider, Skeleton, Typography, Grid, Link } from '@arco-design/web-react'; 3 | import styles from './style/overview.module.less'; 4 | import { IconCalendar, IconCaretUp } from '@arco-design/web-react/icon'; 5 | import ContentData from './contentData'; 6 | 7 | type StatisticItemType = { 8 | icon?: ReactNode; 9 | title?: ReactNode; 10 | count?: ReactNode; 11 | loading?: boolean; 12 | unit?: ReactNode; 13 | }; 14 | 15 | const { Row, Col } = Grid; 16 | 17 | function StatisticItem(props: StatisticItemType) { 18 | const { icon, title, count, loading, unit } = props; 19 | return ( 20 |
21 |
{icon}
22 |
23 | 24 |
{title}
25 |
26 | {count} 27 | {unit} 28 |
29 |
30 |
31 |
32 | ); 33 | } 34 | 35 | const Overview = () => { 36 | return ( 37 | 38 | 欢迎回来,龙风 39 | 40 | 41 | 42 | } title={'线上总数据'} count={1} unit={'个'} loading={false} /> 43 | 44 | 45 | 46 | } title={'投放中的内容'} count={1} unit={'个'} loading={false} /> 47 | 48 | 49 | 50 | } title={'投放中的内容'} count={1} unit={'个'} loading={false} /> 51 | 52 | 53 | 54 | } title={'投放中的内容'} count={1} unit={'个'} loading={false} /> 55 | 56 | 57 | 58 |
59 |
60 | 内容数据 61 | 查看更多 62 |
63 | 64 |
65 | 66 | ); 67 | }; 68 | 69 | export default Overview; 70 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/shortcuts.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Divider, Link, Typography } from '@arco-design/web-react'; 2 | import { IconFile, IconFire, IconMobile, IconSettings, IconStorage } from '@arco-design/web-react/icon'; 3 | import React from 'react'; 4 | import './style/shortcuts.less'; 5 | 6 | const classPrefix = 'shortcuts'; 7 | const ShortCuts = () => { 8 | const shortcutList = [ 9 | { 10 | title: '内容管理', 11 | key: 'Content Management', 12 | icon: , 13 | }, 14 | { 15 | title: '内容数据', 16 | key: 'Content Statistic', 17 | icon: , 18 | }, 19 | { 20 | title: '高级管理', 21 | key: 'Advanced Management', 22 | icon: , 23 | }, 24 | { 25 | title: '线上推广', 26 | key: 'Online Promotion', 27 | icon: , 28 | }, 29 | { 30 | title: '内容投放', 31 | key: 'Marketing', 32 | icon: , 33 | }, 34 | ]; 35 | 36 | const recentShortcuts = [ 37 | { 38 | title: '内容数据', 39 | key: 'Content Statistic', 40 | icon: , 41 | }, 42 | { 43 | title: '内容管理', 44 | key: 'Content Management', 45 | icon: , 46 | }, 47 | { 48 | title: '高级管理', 49 | key: 'Advanced Management', 50 | icon: , 51 | }, 52 | ]; 53 | 54 | return ( 55 | 56 |
57 | 快捷入口 58 | 查看 59 |
60 | 61 |
62 | {shortcutList.map((item) => ( 63 |
64 |
{item.icon}
65 |
{item.title}
66 |
67 | ))} 68 |
69 | 70 |
71 | 最近访问 72 |
73 |
74 | {recentShortcuts.map((item) => ( 75 |
76 |
{item.icon}
77 |
{item.title}
78 |
79 | ))} 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default ShortCuts; 86 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/style/content.module.less: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 20px; 3 | } 4 | .ctw { 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/style/overview.module.less: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 20px; 3 | 4 | :global(.arco-divider-horizontal) { 5 | border-bottom: 1px solid var(--color-border-1); 6 | } 7 | 8 | :global(.arco-divider-vertical) { 9 | border-left: 1px solid var(--color-border-1); 10 | } 11 | } 12 | 13 | .item { 14 | display: flex; 15 | align-items: center; 16 | padding-left: 20px; 17 | color: var(--color-text-1); 18 | } 19 | 20 | .icon { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | width: 54px; 25 | height: 54px; 26 | background-color: var(--color-fill-2); 27 | border-radius: 50%; 28 | margin-right: 12px; 29 | } 30 | 31 | .title { 32 | font-size: 12px; 33 | color: var(--color-text-1); 34 | } 35 | 36 | .count { 37 | font-size: 22px; 38 | font-weight: 600; 39 | color: var(--color-text-1); 40 | 41 | .unit { 42 | font-size: 12px; 43 | font-weight: 400; 44 | color: var(--color-text-2); 45 | margin-left: 8px; 46 | } 47 | } 48 | 49 | .divider { 50 | height: 60px; 51 | } 52 | 53 | .ctw { 54 | display: flex; 55 | justify-content: space-between; 56 | align-items: center; 57 | margin-bottom: 16px; 58 | } 59 | 60 | .chart-title { 61 | font-size: 16px; 62 | font-weight: 500; 63 | } 64 | 65 | .chart-sub-title { 66 | font-size: 12px; 67 | font-weight: 400; 68 | margin-left: 4px; 69 | color: var(--color-text-3); 70 | } 71 | -------------------------------------------------------------------------------- /src/app/Dashboard/WorkPlace/modules/slide/style/shortcuts.less: -------------------------------------------------------------------------------- 1 | @class-prefix-shortcuts: ~'shortcuts'; 2 | 3 | .@{class-prefix-shortcuts} { 4 | &-header { 5 | display: flex; 6 | justify-content: space-between; 7 | } 8 | 9 | &-list { 10 | display: grid; 11 | grid-template-columns: 33.33% 33.33% 33.33%; 12 | } 13 | 14 | &-item { 15 | align-items: center; 16 | box-sizing: border-box; 17 | cursor: pointer; 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: center; 21 | padding: 12px; 22 | 23 | &:hover { 24 | background-color: rgba(0, 0, 0, 0.05); 25 | } 26 | } 27 | 28 | &-title { 29 | font-size: 12px; 30 | line-height: 20px; 31 | color: var(--color-text-1); 32 | } 33 | 34 | &-icon { 35 | align-items: center; 36 | background-color: var(--color-fill-2); 37 | border-radius: 6px; 38 | display: flex; 39 | height: 32px; 40 | justify-content: center; 41 | margin-bottom: 4px; 42 | width: 32px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/Exception/403.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | import useI18n from 'src/ahooks/useI18n'; 4 | import locale from './locales'; 5 | 6 | const Result403 = () => { 7 | const { i18n, lang } = useI18n(locale); 8 | return ( 9 |
10 | 15 | {i18n[lang]['exception.result.403.back']} 16 | 17 | } 18 | /> 19 |
20 | ); 21 | }; 22 | export default Result403; 23 | -------------------------------------------------------------------------------- /src/app/Exception/404.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | import useI18n from 'src/ahooks/useI18n'; 4 | import locale from './locales'; 5 | 6 | const Result404 = () => { 7 | const { lang, i18n } = useI18n(locale); 8 | 9 | return ( 10 |
11 | {i18n[lang]['exception.result.404.retry']}, 16 | , 19 | ]} 20 | /> 21 |
22 | ); 23 | }; 24 | export default Result404; 25 | -------------------------------------------------------------------------------- /src/app/Exception/500.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Result } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | import useI18n from 'src/ahooks/useI18n'; 4 | import locale from './locales'; 5 | 6 | const Result500 = () => { 7 | const { lang, i18n } = useI18n(locale); 8 | return ( 9 |
10 | {i18n[lang]['exception.result.500.retry']}, 15 | , 18 | ]} 19 | /> 20 |
21 | ); 22 | }; 23 | export default Result500; 24 | -------------------------------------------------------------------------------- /src/app/Exception/Building.tsx: -------------------------------------------------------------------------------- 1 | import { Result } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | 4 | const Building = () => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default Building; 13 | -------------------------------------------------------------------------------- /src/app/Exception/locales/en-us.ts: -------------------------------------------------------------------------------- 1 | const en: { 2 | [k: string]: string; 3 | } = { 4 | 'menu.exception': 'Exception page', 5 | 'menu.exception.403': '403', 6 | 'exception.result.403.description': 'Access to this resource on the server is denied.', 7 | 'exception.result.403.back': 'Back', 8 | 9 | 'menu.exception.404': '404', 10 | 'exception.result.404.description': 'Whoops, this page is gone.', 11 | 'exception.result.404.retry': 'Retry', 12 | 'exception.result.404.back': 'Back', 13 | 14 | 'menu.exception.500': '500', 15 | 'exception.result.500.retry': 'Retry', 16 | 'exception.result.500.description': 'Internal server error', 17 | 'exception.result.500.back': 'Back', 18 | }; 19 | 20 | export default en; 21 | -------------------------------------------------------------------------------- /src/app/Exception/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en-us'; 2 | import zh from './zh-cn'; 3 | 4 | export const i18n = { 5 | 'en-US': en, 6 | 'zh-CN': zh, 7 | }; 8 | 9 | export default i18n; 10 | -------------------------------------------------------------------------------- /src/app/Exception/locales/zh-cn.ts: -------------------------------------------------------------------------------- 1 | const zh: { 2 | [k: string]: string; 3 | } = { 4 | 'menu.exception': '异常页', 5 | 'menu.exception.403': '403', 6 | 'exception.result.403.description': '对不起,您没有访问该资源的权限', 7 | 'exception.result.403.back': '返回', 8 | 9 | 'menu.exception.404': '404', 10 | 'exception.result.404.description': '抱歉,页面不见了~', 11 | 'exception.result.404.retry': '重试', 12 | 'exception.result.404.back': '返回', 13 | 14 | 'menu.exception.500': '500', 15 | 'exception.result.500.retry': '重试', 16 | 'exception.result.500.description': '抱歉,服务器出了点问题~', 17 | 'exception.result.500.back': '返回', 18 | }; 19 | 20 | export default zh; 21 | -------------------------------------------------------------------------------- /src/app/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLocalStorageState } from 'ahooks'; 2 | import React, { useEffect } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import LayoutMain from '../../Layout/Layout'; 5 | 6 | export const Home: React.FC = () => { 7 | const navigate = useNavigate(); 8 | const [userToken] = useLocalStorageState('userToken'); 9 | useEffect(() => { 10 | if (!userToken) { 11 | navigate('/login'); 12 | } 13 | }, []); 14 | 15 | return ; 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/Login/index.module.less: -------------------------------------------------------------------------------- 1 | .login { 2 | width: 100%; 3 | display: flex; 4 | height: 100vh; 5 | overflow: hidden; 6 | } 7 | 8 | .logo { 9 | position: absolute; 10 | left: 20px; 11 | top: 10px; 12 | z-index: 2; 13 | color: #fff; 14 | } 15 | 16 | .left { 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | background: linear-gradient(163.85deg, #1d2129, #00308f); 21 | width: 550px; 22 | } 23 | 24 | .item { 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | } 29 | 30 | .right { 31 | flex: 1; 32 | display: flex; 33 | align-items: center; 34 | justify-content: center; 35 | padding-bottom: 40px; 36 | background: var(--color-bg-3); 37 | } 38 | 39 | .title { 40 | margin-bottom: 28px; 41 | font-size: 24px; 42 | font-weight: bold; 43 | color: var(--color-text-1); 44 | } 45 | 46 | .footer { 47 | position: absolute; 48 | bottom: 10px; 49 | text-align: center; 50 | } 51 | -------------------------------------------------------------------------------- /src/app/Login/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import axios from 'axios'; 3 | import { Form, Input, Button, Checkbox, Link, Message } from '@arco-design/web-react'; 4 | import { IconLock, IconUser } from '@arco-design/web-react/icon'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { useLocalStorageState } from 'ahooks'; 7 | import Banner from './modules/Banner'; 8 | import useI18n from 'src/ahooks/useI18n'; 9 | import locales from './locales'; 10 | import styles from './index.module.less'; 11 | import './mock/user'; 12 | import CommonSetting from 'src/components/CommonSetting' 13 | 14 | type IUserParams = { 15 | username: string; 16 | password: string; 17 | }; 18 | const FormItem = Form.Item; 19 | 20 | export const Login: React.FC = () => { 21 | const [form] = Form.useForm(); 22 | const { lang, i18n } = useI18n(locales); 23 | const navigate = useNavigate(); 24 | const [userToken, setUserToken] = useLocalStorageState('userToken'); 25 | const [loading, setLoading] = useState(false); 26 | 27 | useEffect(() => { 28 | // 判断是否登陆 29 | if (userToken) { 30 | navigate('/weclome'); 31 | } 32 | }, []); 33 | 34 | const onSubmit = () => { 35 | form.validate((err, values) => { 36 | if (err) { 37 | return; 38 | } 39 | const { username, password } = values; 40 | login({ username, password }); 41 | }); 42 | }; 43 | 44 | const login = (params: IUserParams) => { 45 | setLoading(true); 46 | axios 47 | .post('/api/user/login', params) 48 | .then((res) => { 49 | const { 50 | status, 51 | msg, 52 | data: { token }, 53 | } = res.data; 54 | console.log(msg); 55 | if (status === 'ok') { 56 | Message.success('登录成功'); 57 | navigate('/weclome'); 58 | setUserToken(token); 59 | } else { 60 | Message.error(msg); 61 | } 62 | }) 63 | .finally(() => { 64 | setLoading(false); 65 | }); 66 | }; 67 | 68 | return ( 69 |
70 |
71 |

React Arco Admin

72 |
73 |
74 | 75 |
76 |
77 |
78 |
登录React Arco Admin
79 |
91 | 95 | } type="text" placeholder="username:admin" /> 96 | 97 | 101 | } placeholder="password:admin" visibilityToggle /> 102 | 103 | 104 |
105 | {i18n[lang]['login.rememberPassword']} 106 | {i18n[lang]['login.forgetPassword']} 107 |
108 |
109 | 110 | 113 | 114 | 115 | 118 | 119 | 120 |
121 |
鄂ICP备18026800号-1
122 |
123 | 124 |
125 | ); 126 | }; 127 | -------------------------------------------------------------------------------- /src/app/Login/locales/en-us.ts: -------------------------------------------------------------------------------- 1 | const en: { 2 | [k: string]: string; 3 | } = { 4 | 'login.forgetPassword': 'forget password', 5 | 'login.rememberPassword': 'remember password', 6 | 'login.login': 'login', 7 | 'login.registerAccount': 'register account', 8 | 'login.username.isNotEmpty': 'the username cannot be empty', 9 | 'login.password.isNotEmpty': 'the password cannot be empty', 10 | }; 11 | 12 | export default en; 13 | -------------------------------------------------------------------------------- /src/app/Login/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en-us'; 2 | import zh from './zh-cn'; 3 | 4 | export const i18n = { 5 | 'en-US': en, 6 | 'zh-CN': zh, 7 | }; 8 | 9 | export default i18n; 10 | -------------------------------------------------------------------------------- /src/app/Login/locales/zh-cn.ts: -------------------------------------------------------------------------------- 1 | const zh: { 2 | [k: string]: string; 3 | } = { 4 | 'login.forgetPassword': '忘记密码', 5 | 'login.rememberPassword': '记住密码', 6 | 'login.login': '登录', 7 | 'login.registerAccount': '注册账号', 8 | 'login.username.isNotEmpty': '用户名不能为空', 9 | 'login.password.isNotEmpty': '密码不能为空' 10 | }; 11 | 12 | export default zh; 13 | -------------------------------------------------------------------------------- /src/app/Login/mock/user.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | import { uuid } from 'src/utils' 3 | import setupMock from 'src/utils/setupMock'; 4 | 5 | setupMock({ 6 | setup: () => { 7 | Mock.mock(new RegExp('/api/user/login'), (params: { body: string }) => { 8 | const { username, password } = JSON.parse(params.body); 9 | if (!username) { 10 | return { 11 | status: 'error', 12 | msg: '用户名不能为空', 13 | }; 14 | } 15 | if (!password) { 16 | return { 17 | status: 'error', 18 | msg: '密码不能为空', 19 | }; 20 | } 21 | if (username === 'admin' && password === 'admin') { 22 | return { 23 | status: 'ok', 24 | data: { 25 | token: uuid(), 26 | }, 27 | }; 28 | } 29 | return { 30 | status: 'error', 31 | msg: '账号或者密码错误', 32 | }; 33 | }); 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/Login/modules/Banner.tsx: -------------------------------------------------------------------------------- 1 | import { Carousel } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | 4 | const Banner = () => { 5 | const list = ['人生在勤,不索何获.', '三十以前,不要怕;三十以后,不要悔。', '真理是永远蒙蔽不了的。']; 6 | return ( 7 |
8 | 14 | {list.map((item, index) => ( 15 |
16 |

{item}

17 |
18 | ))} 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default Banner; 25 | -------------------------------------------------------------------------------- /src/app/Result/Error.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Result, Button } from '@arco-design/web-react' 3 | import useI18n from 'src/ahooks/useI18n' 4 | import locale from './locales' 5 | 6 | const Error = () => { 7 | const { lang, i18n } = useI18n(locale) 8 | return ( 9 |
10 | 16 | {i18n[lang]['menu.result.error.again']} 17 | , 18 | , 21 | ]} 22 | /> 23 |
24 | ) 25 | } 26 | 27 | export default Error 28 | -------------------------------------------------------------------------------- /src/app/Result/Success.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Result, Button } from '@arco-design/web-react' 3 | import useI18n from 'src/ahooks/useI18n' 4 | import locale from './locales' 5 | 6 | const Success = () => { 7 | const { lang, i18n } = useI18n(locale) 8 | return ( 9 |
10 | 16 | {i18n[lang]['menu.result.success.again']} 17 | , 18 | , 21 | ]} 22 | /> 23 |
24 | ) 25 | } 26 | 27 | export default Success 28 | -------------------------------------------------------------------------------- /src/app/Result/locales/en-us.ts: -------------------------------------------------------------------------------- 1 | const en: { 2 | [k: string]: string 3 | } = { 4 | 'menu.result': 'Result page', 5 | 6 | 'menu.result.success': 'success', 7 | 'menu.result.success.title': 'Success message', 8 | 'menu.result.success.subTitle': 'This is a success description.', 9 | 'menu.result.success.again': 'Again', 10 | 'menu.result.success.back': 'Back', 11 | 12 | 'menu.result.error': 'fail', 13 | 'menu.result.error.title': 'Error message', 14 | 'menu.result.error.subTitle': 'Something went wrong. Please try again.', 15 | 'menu.result.error.again': 'Again', 16 | 'menu.result.error.back': 'Back', 17 | } 18 | 19 | export default en 20 | -------------------------------------------------------------------------------- /src/app/Result/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en-us'; 2 | import zh from './zh-cn'; 3 | 4 | export const i18n = { 5 | 'en-US': en, 6 | 'zh-CN': zh, 7 | }; 8 | 9 | export default i18n; 10 | -------------------------------------------------------------------------------- /src/app/Result/locales/zh-cn.ts: -------------------------------------------------------------------------------- 1 | const zh: { 2 | [k: string]: string 3 | } = { 4 | 'menu.result': '结果页', 5 | 6 | 'menu.result.success': '成功页', 7 | 'menu.result.success.title': '成功信息', 8 | 'menu.result.success.subTitle': '这是一个成功的描述', 9 | 'menu.result.success.again': '重试', 10 | 'menu.result.success.back': '返回', 11 | 12 | 'menu.result.error': '失败', 13 | 'menu.result.error.title': '失败信息', 14 | 'menu.result.error.subTitle': '发生了一些错误。请重试。', 15 | 'menu.result.error.again': '重试', 16 | 'menu.result.error.back': '返回', 17 | } 18 | 19 | export default zh 20 | -------------------------------------------------------------------------------- /src/app/User/Info/Info.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './index'; 4 | 5 | const classPrefix = 'user-info'; 6 | 7 | const Info = () => { 8 | return ( 9 |
10 |

11 |
12 | ); 13 | }; 14 | 15 | export default Info; 16 | -------------------------------------------------------------------------------- /src/app/User/Info/index.less: -------------------------------------------------------------------------------- 1 | @class-prefix-user: ~'user-info'; 2 | 3 | .@{class-prefix-user} { 4 | position: relative; 5 | .@{class-prefix-user}-avatar { 6 | position: absolute; 7 | right: 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/User/setting/index.module.less: -------------------------------------------------------------------------------- 1 | .setting { 2 | width: 100%; 3 | height: 100%; 4 | background-color: transparent; 5 | 6 | .card { 7 | display: flex; 8 | flex-direction: row; 9 | } 10 | 11 | .avatar { 12 | margin-right: 50px; 13 | } 14 | 15 | .content { 16 | flex: 1; 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/User/setting/index.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Button, Card, Descriptions } from '@arco-design/web-react' 2 | import React, { useContext, useReducer } from 'react'; 3 | import styles from './index.module.less' 4 | 5 | const initialState = { data: [{ 6 | label: 'Name', 7 | value: 'Socrates', 8 | }, { 9 | label: 'Mobile', 10 | value: '123-1234-1234', 11 | }, { 12 | label: 'Residence', 13 | value: 'Beijing' 14 | }, { 15 | label: 'Hometown', 16 | value: 'Beijing', 17 | }, { 18 | label: 'Address', 19 | value: 'Yingdu Building, Zhichun Road, Beijing' 20 | }] 21 | }; 22 | 23 | type Action = { type: "update" } 24 | function reducer(state: typeof initialState, action: Action) { 25 | switch (action.type) { 26 | case 'update': 27 | return { 28 | ...state, 29 | data: state.data.splice(0, 1) 30 | } 31 | } 32 | } 33 | 34 | interface ContextState { 35 | data?: typeof initialState, 36 | dispatch?: React.Dispatch 37 | } 38 | const MyContenxt = React.createContext({}) 39 | 40 | const Setting = () => { 41 | const [state, dispatch] = useReducer(reducer, initialState) 42 | return ( 43 |
44 |

useReducer 和 useContext 的最佳实践

45 | 49 | 52 |
53 | 54 | 55 | 56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 | ) 64 | } 65 | export default Setting 66 | 67 | const UserInfo = () => { 68 | const {data, dispatch} = useContext(MyContenxt) 69 | const change = () => { 70 | dispatch && dispatch({ 71 | type: 'update' 72 | }) 73 | } 74 | return ( 75 |
76 | 83 | 84 |
85 | 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /src/app/Visualization/DataAnalysis/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DataAnalysis = () => { 4 | return ( 5 |
6 |

数据分析页

7 |
8 | ); 9 | }; 10 | 11 | export default DataAnalysis; 12 | -------------------------------------------------------------------------------- /src/app/Visualization/MultiDimensionDataAnalysis/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const MultiDimensionDataAnalysis = () => { 4 | return ( 5 |
6 |

MultiDimensionDataAnalysis

7 |
8 | ); 9 | }; 10 | 11 | export default MultiDimensionDataAnalysis; 12 | -------------------------------------------------------------------------------- /src/app/Welcome/index.module.less: -------------------------------------------------------------------------------- 1 | .container { 2 | background: lightblue; 3 | cursor: pointer; 4 | flex: 1; 5 | display: flex; 6 | align-items: center; 7 | height: 100%; 8 | justify-content: center; 9 | overflow: hidden; 10 | } 11 | .deck { 12 | position: absolute; 13 | width: 300px; 14 | height: 200px; 15 | will-change: transform; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | touch-action: none; 20 | } 21 | 22 | .deck > div { 23 | background-color: white; 24 | background-size: auto 85%; 25 | background-repeat: no-repeat; 26 | background-position: center center; 27 | width: 45vh; 28 | max-width: 150px; 29 | height: 85vh; 30 | max-height: 285px; 31 | will-change: transform; 32 | border-radius: 10px; 33 | box-shadow: 0 12.5px 100px -10px rgba(50, 50, 73, 0.4), 0 10px 10px -10px rgba(50, 50, 73, 0.3); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/Welcome/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Home = () => { 4 | return
hello
5 | } 6 | 7 | export default Home 8 | -------------------------------------------------------------------------------- /src/assets/css/comom.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | ::-webkit-scrollbar { 9 | width: 0px; 10 | } 11 | 12 | :root { 13 | --theme-color: #873bf4; 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/css/index.css: -------------------------------------------------------------------------------- 1 | @import './comom.css'; 2 | @import './reset.css'; 3 | @import './reset-arco.css'; 4 | -------------------------------------------------------------------------------- /src/assets/css/nprogress.less: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | @main-color: var(--theme-color); 3 | 4 | #nprogress { 5 | pointer-events: none; 6 | } 7 | 8 | #nprogress .bar { 9 | background: @main-color; 10 | 11 | position: fixed; 12 | z-index: 1031; 13 | top: 0; 14 | left: 0; 15 | 16 | width: 100%; 17 | height: 2px; 18 | } 19 | 20 | /* Fancy blur effect */ 21 | #nprogress .peg { 22 | display: block; 23 | position: absolute; 24 | right: 0px; 25 | width: 100px; 26 | height: 100%; 27 | box-shadow: 0 0 10px @main-color, 0 0 5px @main-color; 28 | opacity: 1; 29 | 30 | -webkit-transform: rotate(3deg) translate(0px, -4px); 31 | -ms-transform: rotate(3deg) translate(0px, -4px); 32 | transform: rotate(3deg) translate(0px, -4px); 33 | } 34 | 35 | /* Remove these to get rid of the spinner */ 36 | #nprogress .spinner { 37 | display: block; 38 | position: fixed; 39 | z-index: 1031; 40 | top: 15px; 41 | right: 15px; 42 | } 43 | 44 | #nprogress .spinner-icon { 45 | width: 18px; 46 | height: 18px; 47 | box-sizing: border-box; 48 | 49 | border: solid 2px transparent; 50 | border-top-color: @main-color; 51 | border-left-color: @main-color; 52 | border-radius: 50%; 53 | 54 | -webkit-animation: nprogress-spinner 400ms linear infinite; 55 | animation: nprogress-spinner 400ms linear infinite; 56 | } 57 | 58 | .nprogress-custom-parent { 59 | overflow: hidden; 60 | position: relative; 61 | } 62 | 63 | .nprogress-custom-parent #nprogress .spinner, 64 | .nprogress-custom-parent #nprogress .bar { 65 | position: absolute; 66 | } 67 | 68 | @-webkit-keyframes nprogress-spinner { 69 | 0% { 70 | -webkit-transform: rotate(0deg); 71 | } 72 | 100% { 73 | -webkit-transform: rotate(360deg); 74 | } 75 | } 76 | @keyframes nprogress-spinner { 77 | 0% { 78 | transform: rotate(0deg); 79 | } 80 | 100% { 81 | transform: rotate(360deg); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/assets/css/reset-arco.css: -------------------------------------------------------------------------------- 1 | .arco-layout-has-sider { 2 | overflow: hidden; 3 | } -------------------------------------------------------------------------------- /src/assets/css/reset.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { 178 | /* 1 */ 179 | overflow: visible; 180 | } 181 | 182 | /** 183 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 184 | * 1. Remove the inheritance of text transform in Firefox. 185 | */ 186 | 187 | button, 188 | select { 189 | /* 1 */ 190 | text-transform: none; 191 | } 192 | 193 | /** 194 | * Correct the inability to style clickable types in iOS and Safari. 195 | */ 196 | 197 | button, 198 | [type="button"], 199 | [type="reset"], 200 | [type="submit"] { 201 | -webkit-appearance: button; 202 | } 203 | 204 | /** 205 | * Remove the inner border and padding in Firefox. 206 | */ 207 | 208 | button::-moz-focus-inner, 209 | [type="button"]::-moz-focus-inner, 210 | [type="reset"]::-moz-focus-inner, 211 | [type="submit"]::-moz-focus-inner { 212 | border-style: none; 213 | padding: 0; 214 | } 215 | 216 | /** 217 | * Restore the focus styles unset by the previous rule. 218 | */ 219 | 220 | button:-moz-focusring, 221 | [type="button"]:-moz-focusring, 222 | [type="reset"]:-moz-focusring, 223 | [type="submit"]:-moz-focusring { 224 | outline: 1px dotted ButtonText; 225 | } 226 | 227 | /** 228 | * Correct the padding in Firefox. 229 | */ 230 | 231 | fieldset { 232 | padding: 0.35em 0.75em 0.625em; 233 | } 234 | 235 | /** 236 | * 1. Correct the text wrapping in Edge and IE. 237 | * 2. Correct the color inheritance from `fieldset` elements in IE. 238 | * 3. Remove the padding so developers are not caught out when they zero out 239 | * `fieldset` elements in all browsers. 240 | */ 241 | 242 | legend { 243 | box-sizing: border-box; /* 1 */ 244 | color: inherit; /* 2 */ 245 | display: table; /* 1 */ 246 | max-width: 100%; /* 1 */ 247 | padding: 0; /* 3 */ 248 | white-space: normal; /* 1 */ 249 | } 250 | 251 | /** 252 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 253 | */ 254 | 255 | progress { 256 | vertical-align: baseline; 257 | } 258 | 259 | /** 260 | * Remove the default vertical scrollbar in IE 10+. 261 | */ 262 | 263 | textarea { 264 | overflow: auto; 265 | } 266 | 267 | /** 268 | * 1. Add the correct box sizing in IE 10. 269 | * 2. Remove the padding in IE 10. 270 | */ 271 | 272 | [type="checkbox"], 273 | [type="radio"] { 274 | box-sizing: border-box; /* 1 */ 275 | padding: 0; /* 2 */ 276 | } 277 | 278 | /** 279 | * Correct the cursor style of increment and decrement buttons in Chrome. 280 | */ 281 | 282 | [type="number"]::-webkit-inner-spin-button, 283 | [type="number"]::-webkit-outer-spin-button { 284 | height: auto; 285 | } 286 | 287 | /** 288 | * 1. Correct the odd appearance in Chrome and Safari. 289 | * 2. Correct the outline style in Safari. 290 | */ 291 | 292 | [type="search"] { 293 | -webkit-appearance: textfield; /* 1 */ 294 | outline-offset: -2px; /* 2 */ 295 | } 296 | 297 | /** 298 | * Remove the inner padding in Chrome and Safari on macOS. 299 | */ 300 | 301 | [type="search"]::-webkit-search-decoration { 302 | -webkit-appearance: none; 303 | } 304 | 305 | /** 306 | * 1. Correct the inability to style clickable types in iOS and Safari. 307 | * 2. Change font properties to `inherit` in Safari. 308 | */ 309 | 310 | ::-webkit-file-upload-button { 311 | -webkit-appearance: button; /* 1 */ 312 | font: inherit; /* 2 */ 313 | } 314 | 315 | /* Interactive 316 | ========================================================================== */ 317 | 318 | /* 319 | * Add the correct display in Edge, IE 10+, and Firefox. 320 | */ 321 | 322 | details { 323 | display: block; 324 | } 325 | 326 | /* 327 | * Add the correct display in all browsers. 328 | */ 329 | 330 | summary { 331 | display: list-item; 332 | } 333 | 334 | /* Misc 335 | ========================================================================== */ 336 | 337 | /** 338 | * Add the correct display in IE 10+. 339 | */ 340 | 341 | template { 342 | display: none; 343 | } 344 | 345 | /** 346 | * Add the correct display in IE 10. 347 | */ 348 | 349 | [hidden] { 350 | display: none; 351 | } 352 | -------------------------------------------------------------------------------- /src/assets/images/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/CommonSetting/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@arco-design/web-react'; 2 | import { IconSettings } from '@arco-design/web-react/icon'; 3 | import React from 'react'; 4 | import PageConfig from '../PageConifg/PageConfig'; 5 | 6 | const Index = () => { 7 | return ( 8 | 9 | 12 | 13 | ); 14 | }; 15 | 16 | export default Index; 17 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spin, SpinProps } from '@arco-design/web-react'; 2 | import React from 'react'; 3 | 4 | const Loading: React.FC = (props) => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default Loading; 13 | -------------------------------------------------------------------------------- /src/components/PageConifg/PageConfig.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Drawer, Trigger, Select } from '@arco-design/web-react'; 3 | import { SketchPicker } from 'react-color'; 4 | import { changePageColor } from 'src/utils'; 5 | import classnames from 'classnames'; 6 | import { useColor } from 'src/ahooks'; 7 | import { IconLanguage } from '@arco-design/web-react/icon'; 8 | import useI18n from 'src/ahooks/useI18n'; 9 | import { ILang } from 'src/utils/GlobalContext'; 10 | 11 | type IProps = { 12 | children: React.ReactNode; 13 | }; 14 | 15 | const { Option } = Select; 16 | 17 | const PageConfig: React.FC = (props) => { 18 | const { children } = props; 19 | const [color, setColor] = useState(''); 20 | const [visible, setVisible] = useState(false); 21 | const [language, setLanguage] = useState('zh-CN'); 22 | const [themeColor, setThemeColor] = useColor(); 23 | const { lang, setLang, i18n } = useI18n(); 24 | useEffect(() => { 25 | setColor(themeColor); 26 | setLanguage(lang); 27 | }, [visible]); 28 | 29 | const onOk = () => { 30 | setVisible(false); 31 | setThemeColor(color); 32 | changePageColor(color); 33 | setLang(language); 34 | }; 35 | 36 | return ( 37 |
38 | {i18n[lang]['system.tip.config']}} 43 | visible={visible} 44 | onOk={onOk} 45 | onCancel={() => { 46 | setVisible(false); 47 | }} 48 | > 49 | ( 51 | { 54 | setColor(value.hex); 55 | }} 56 | /> 57 | )} 58 | mouseEnterDelay={400} 59 | mouseLeaveDelay={400} 60 | position="bottom" 61 | > 62 |
63 |

{i18n[lang]['system.tip.themColor']}

64 |
67 |
{color}
68 |
69 |
70 | 71 |
72 |
73 | 74 | Languages 75 |
76 | 85 |
86 |
87 |
setVisible(true)}>{children}
88 |
89 | ); 90 | }; 91 | 92 | export default PageConfig; 93 | -------------------------------------------------------------------------------- /src/conifg/index.ts: -------------------------------------------------------------------------------- 1 | export const url = {} 2 | -------------------------------------------------------------------------------- /src/conifg/menuConfig.tsx: -------------------------------------------------------------------------------- 1 | export interface IMenusItem { 2 | name: string; 3 | key: string; 4 | path: string; 5 | icon?: string; 6 | children?: { 7 | name: string; 8 | key: string; 9 | path: string; 10 | }[]; 11 | } 12 | interface IMenu { 13 | menu: IMenusItem[]; 14 | } 15 | 16 | export const menuConfig: IMenu = { 17 | menu: [ 18 | { 19 | name: 'menu.dashboard', 20 | key: '/dashboard', 21 | path: '/dashboard', 22 | icon: 'IconDashboard', 23 | children: [ 24 | { 25 | name: 'menu.dashboard.workplace', 26 | key: '/dashboard/workplace', 27 | path: '/dashboard/workplace', 28 | }, 29 | { 30 | name: 'menu.dashboard.monitor', 31 | key: '/dashboard/monitor', 32 | path: '/dashboard/monitor', 33 | }, 34 | ], 35 | }, 36 | { 37 | name: 'menu.visualization', 38 | key: '/visualization', 39 | path: '/visualization', 40 | icon: 'IconHome', 41 | children: [ 42 | { 43 | name: 'menu.visualization.dataAnalysis', 44 | key: '/visualization/data-analysis', 45 | path: '/visualization/data-analysis', 46 | }, 47 | { 48 | name: 'menu.visualization.multiDimensionDataAnalysis', 49 | key: '/visualization/multi-dimension-data-analysis', 50 | path: '/visualization/multi-dimension-data-analysis', 51 | }, 52 | ], 53 | }, 54 | { 55 | name: 'menu.list', 56 | key: '/list', 57 | path: '/list', 58 | icon: 'IconList', 59 | children: [ 60 | { 61 | name: 'menu.list.searchTable', 62 | key: '/list/search-table', 63 | path: '/list/search-table', 64 | }, 65 | { 66 | name: 'menu.list.cardList', 67 | key: '/list/card', 68 | path: '/list/card', 69 | }, 70 | ], 71 | }, 72 | { 73 | name: 'menu.profile', 74 | key: '/profile', 75 | path: '/profile', 76 | icon: 'IconFile', 77 | children: [ 78 | { 79 | name: 'menu.profile.basic', 80 | key: '/profile/basic', 81 | path: '/profile/basic', 82 | }, 83 | ], 84 | }, 85 | { 86 | name: 'menu.result', 87 | key: '/result', 88 | path: '/result', 89 | icon: 'IconCheckCircle', 90 | children: [ 91 | { 92 | name: 'menu.result.success', 93 | key: '/result/success', 94 | path: '/result/success', 95 | }, 96 | { 97 | name: 'menu.result.error', 98 | key: '/result/error', 99 | path: '/result/error', 100 | }, 101 | ], 102 | }, 103 | { 104 | name: 'menu.exception', 105 | key: '/exception', 106 | path: '/exception', 107 | icon: 'IconExclamationCircle', 108 | children: [ 109 | { 110 | name: 'menu.exception.403', 111 | key: '/exception/403', 112 | path: '/exception/403', 113 | }, 114 | { 115 | name: 'menu.exception.404', 116 | key: '/exception/404', 117 | path: '/exception/404', 118 | }, 119 | { 120 | name: 'menu.exception.500', 121 | key: '/exception/500', 122 | path: '/exception/500', 123 | }, 124 | ], 125 | }, 126 | { 127 | name: 'menu.user', 128 | key: '/user', 129 | path: '/user', 130 | icon: 'IconUser', 131 | children: [ 132 | { 133 | name: 'menu.user.info', 134 | key: '/user/info', 135 | path: '/user/info', 136 | }, 137 | { 138 | name: 'menu.user.setting', 139 | key: '/user/setting', 140 | path: '/user/setting', 141 | }, 142 | ], 143 | }, 144 | ], 145 | }; 146 | -------------------------------------------------------------------------------- /src/conifg/routerConfig.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Result403 from 'src/app/Exception/403'; 3 | import Result404 from 'src/app/Exception/404'; 4 | import Result500 from 'src/app/Exception/500'; 5 | 6 | const Welcome = React.lazy(() => import('../app/Welcome')); 7 | const Workplace = React.lazy(() => import('../app/Dashboard/WorkPlace/WorkPlace')); 8 | const Monitor = React.lazy(() => import('../app/Dashboard/Monitor/Monitor')); 9 | const DataAnalysis = React.lazy(() => import('../app/Visualization/DataAnalysis')); 10 | const MultiDimensionDataAnalysis = React.lazy(() => import('../app/Visualization/MultiDimensionDataAnalysis')); 11 | const Setting = React.lazy(() => import('../app/User/setting')); 12 | 13 | const Success = React.lazy(() => import('../app/Result/Success')); 14 | const Error = React.lazy(() => import('../app/Result/Error')); 15 | 16 | export interface IRouterConfig { 17 | path: string; 18 | text: string; 19 | page: React.ReactElement; 20 | } 21 | 22 | const RouterConfig: IRouterConfig[] = [ 23 | { 24 | path: '/welcome', 25 | text: '欢迎页', 26 | page: , 27 | }, 28 | { 29 | path: '/dashboard/workplace', 30 | text: '工作台', 31 | page: , 32 | }, 33 | { 34 | path: '/dashboard/monitor', 35 | text: '实时监控', 36 | page: , 37 | }, 38 | { 39 | path: '/visualization/data-analysis', 40 | text: '分析页', 41 | page: , 42 | }, 43 | { 44 | path: '/visualization/multi-dimension-data-analysis', 45 | text: '多位数据分析', 46 | page: , 47 | }, 48 | { 49 | path: '/exception/403', 50 | text: '403', 51 | page: , 52 | }, 53 | { 54 | path: '/exception/404', 55 | text: '404', 56 | page: , 57 | }, 58 | { 59 | path: '/exception/500', 60 | text: '500', 61 | page: , 62 | }, 63 | { 64 | path: '/result/success', 65 | text: 'success', 66 | page: , 67 | }, 68 | { 69 | path: '/result/error', 70 | text: 'error', 71 | page: , 72 | }, 73 | { 74 | path: '/user/setting', 75 | text: '用户设置', 76 | page: , 77 | }, 78 | ]; 79 | 80 | export default RouterConfig; 81 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 16px; 3 | } 4 | .flex { 5 | display: flex; 6 | } 7 | 8 | .align-items-center { 9 | align-items: center; 10 | } 11 | 12 | .justify-content-center { 13 | justify-content: center; 14 | } 15 | 16 | .justify-content-between { 17 | justify-content: between; 18 | } 19 | 20 | .justify-content-center { 21 | justify-content: center; 22 | } 23 | 24 | .for-loop(@index) when (@index > 0) { 25 | .ml-@{index} { 26 | margin-left: (1px * @index); 27 | } 28 | .mr-@{index} { 29 | margin-right: (1px * @index); 30 | } 31 | .mt-@{index} { 32 | margin-top: (1px * @index); 33 | } 34 | .mb-@{index} { 35 | margin-bottom: (1px * @index); 36 | } 37 | .for-loop(@index - 1); 38 | } 39 | 40 | .for-loop(20); 41 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './assets/css/index.css'; 4 | import App from './App'; 5 | import './mock'; 6 | 7 | const container = document.getElementById('root'); 8 | if (container) { 9 | const root = ReactDOM.createRoot(container); 10 | root.render(); 11 | } 12 | -------------------------------------------------------------------------------- /src/locales/en-us.ts: -------------------------------------------------------------------------------- 1 | const en: { 2 | [k: string]: string; 3 | } = { 4 | 'header.toggletoLight': 'Click to switch to light mode', 5 | 'header.toggletoDark': 'Click to switch to dark mode', 6 | 'header.enterFullScreenMode': 'Full screen', 7 | 'header.leaveFullScreenMode': 'Mini screen', 8 | 'header.userSetting': 'User Setting', 9 | 'header.logout': 'Logout', 10 | 'menu.dashboard': 'Dashboard', 11 | 'menu.dashboard.workplace': 'Workplace', 12 | 'menu.dashboard.monitor': 'Monitor', 13 | 'menu.visualization': 'Data Visualization', 14 | 'menu.visualization.dataAnalysis': 'Analysis', 15 | 'menu.visualization.multiDimensionDataAnalysis': 'Multi-D Analysis', 16 | 'menu.list': 'List', 17 | 'menu.list.searchTable': 'Search Table', 18 | 'menu.list.cardList': 'Card List', 19 | 'menu.profile': 'Profile', 20 | 'menu.profile.basic': 'Basic Profile', 21 | 'menu.result': 'Result', 22 | 'menu.result.success': 'Success', 23 | 'menu.result.error': 'Error', 24 | 'menu.exception': 'Exception', 25 | 'menu.exception.403': '403', 26 | 'menu.exception.404': '404', 27 | 'menu.exception.500': '500', 28 | 'menu.user': 'User Center', 29 | 'menu.user.info': 'User Info', 30 | 'menu.user.setting': 'User Setting', 31 | 32 | 'system.tip.ok': 'ok', 33 | 'system.tip.cancel': 'cancel', 34 | 'system.tip.config': 'page basic config', 35 | 'system.tip.themColor': 'theme color' 36 | }; 37 | 38 | export default en; 39 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import en from './en-us'; 2 | import zh from './zh-cn'; 3 | 4 | export type II18n = { 5 | 'en-US': { [k: string]: string }; 6 | 'zh-CN': { [k: string]: string }; 7 | }; 8 | export const i18n: II18n = { 9 | 'en-US': en, 10 | 'zh-CN': zh, 11 | }; 12 | 13 | export default i18n; 14 | -------------------------------------------------------------------------------- /src/locales/zh-cn.ts: -------------------------------------------------------------------------------- 1 | const zh: { 2 | [k: string]: string 3 | } = { 4 | 'header.toggletoLight': '点击切换为亮色模式', 5 | 'header.toggletoDark': '点击进入暗黑模式', 6 | 'header.enterFullScreenMode': '进入全屏模式', 7 | 'header.leaveFullScreenMode': '退出全屏模式', 8 | 'header.userSetting': '用户设置', 9 | 'header.logout': '退出登录', 10 | 11 | 'menu.dashboard': '仪表盘', 12 | 'menu.dashboard.workplace': '工作台', 13 | 'menu.dashboard.monitor': '实时监控', 14 | 'menu.visualization': '数据可视化', 15 | 'menu.visualization.dataAnalysis': '分析页', 16 | 'menu.visualization.multiDimensionDataAnalysis': '多维数据分析', 17 | 'menu.list': '列表页', 18 | 'menu.list.searchTable': '查询表格', 19 | 'menu.list.cardList': '卡片列表', 20 | 'menu.profile': '详情页', 21 | 'menu.profile.basic': '基础详情页', 22 | 'menu.result': '结果页', 23 | 'menu.result.success': '成功页', 24 | 'menu.result.error': '失败页', 25 | 'menu.exception': '异常页', 26 | 'menu.exception.403': '403', 27 | 'menu.exception.404': '404', 28 | 'menu.exception.500': '500', 29 | 'menu.user': '个人中心', 30 | 'menu.user.info': '用户信息', 31 | 'menu.user.setting': '用户设置', 32 | 33 | 34 | 'system.tip.ok': '确定', 35 | 'system.tip.cancel': '取消', 36 | 'system.tip.config': '页面基本配置', 37 | 'system.tip.themColor': 'theme color' 38 | }; 39 | 40 | export default zh; 41 | -------------------------------------------------------------------------------- /src/mock/index.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | Mock.setup({ 3 | timeout: '500-1500', 4 | }); 5 | -------------------------------------------------------------------------------- /src/utils/GlobalContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export type ILang = 'zh-CN' | 'en-US' 4 | type IContentProps = { 5 | lang: ILang; 6 | setLang: (value: ILang) => void; 7 | }; 8 | export const GlobalContext = createContext({ 9 | lang: 'zh-CN', 10 | setLang: () => {} 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { generate, getRgbStr } from '@arco-design/color' 2 | import { v4 as uuidv4 } from 'uuid' 3 | 4 | /** 5 | * 修改主题色 6 | * @param newColor 7 | */ 8 | export function changePageColor(newColor: string) { 9 | const newList = generate(newColor, { 10 | list: true, 11 | }) 12 | 13 | newList.forEach((l: string, index: number) => { 14 | const rgbStr = getRgbStr(l) 15 | document.body.style.setProperty(`--arcoblue-${index + 1}`, rgbStr) 16 | }) 17 | document.body.style.setProperty('--theme-color', newColor) 18 | } 19 | 20 | export function uuid() { 21 | return uuidv4() 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/setupMock.ts: -------------------------------------------------------------------------------- 1 | export default (config: { mock?: boolean; setup: () => void }) => { 2 | // 这个环境可以不用判断,目前都可以 3 | const { mock = true, setup } = config; 4 | if (mock === false) { 5 | return; 6 | } 7 | setup(); 8 | }; 9 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Arco Admin中台管理系统 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /template/js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yaogeng.zhu 3 | * @Date: 2021-10-08 10:17:01 4 | * @Last Modified by: yaogeng.zhu 5 | * @Last Modified time: 2022-08-12 13:17:24 6 | * 存放一些其他的js脚本 7 | */ 8 | console.log('%c%s', 'color: red; background: yellow; font-size: 24px;', '数据埋点!') 9 | 10 | function buriedPoint() { 11 | const timingInfo = window.performance.timing 12 | const { domLoading, fetchStart } = timingInfo 13 | const time = domLoading - fetchStart 14 | console.log(time + 'ms', '白屏时间') 15 | console.log(timingInfo, '时间统计数据') 16 | } 17 | 18 | window.onload = function () { 19 | buriedPoint() 20 | 21 | window.addEventListener('hashchange', buriedPoint) 22 | history.pushState = new Proxy(history.pushState, { 23 | apply: function (target, thisBinding, args) { 24 | buriedPoint() 25 | return target.apply(thisBinding, args) 26 | }, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度 4 | "diagnostics": true, // 打印诊断信息 5 | "removeComments": true, // 移除代码注释 6 | "sourceMap": true, 7 | "types": ["react"], 8 | // "types": ["react/next", "react-dom/next"], 9 | // 基本配置 10 | "target": "ES5", // 编译成哪个版本的 es 11 | "module": "ESNext", // 指定生成哪个模块系统代码 12 | "lib": ["dom", "dom.iterable", "esnext"], // 编译过程中需要引入的库文件的列表 13 | "allowJs": true, // 允许编译 js 文件 14 | "jsx": "react", // 在 .tsx 文件里支持 JSX 15 | "isolatedModules": true, // 提供额外的一些语法检查,如文件没有模块导出会报错 16 | "strict": true, // 启用所有严格类型检查选项 17 | "noImplicitAny": true, // 不允许隐式的 any 类型 18 | // 模块解析选项 19 | "moduleResolution": "node", // 指定模块解析策略 20 | "esModuleInterop": true, // 支持 CommonJS 和 ES 模块之间的互操作性 21 | "resolveJsonModule": true, // 支持导入 json 模块 22 | "baseUrl": "./", // 根路径 23 | "paths": { 24 | // 路径映射,与 baseUrl 关联 25 | "src/*": ["src/*"], 26 | "components/*": ["src/components/*"], 27 | "utils/*": ["src/utils/*"] 28 | }, 29 | 30 | // 实验性选项 31 | "experimentalDecorators": true, // 启用实验性的ES装饰器 32 | "emitDecoratorMetadata": true, // 给源码里的装饰器声明加上设计类型元数据 33 | 34 | // 其他选项 35 | "forceConsistentCasingInFileNames": true, // 禁止对同一个文件的不一致的引用 36 | "skipLibCheck": true, // 忽略所有的声明文件( *.d.ts)的类型检查 37 | "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入 38 | "noEmit": true // 只想使用tsc的类型检查作为函数时(当其他工具(例如Babel实际编译)时)使用它 39 | }, 40 | "exclude": ["node_modules"] 41 | } 42 | --------------------------------------------------------------------------------