From 3ca4e2a028ffd191b0eafe821ac63cc78b6e57a5 Mon Sep 17 00:00:00 2001 From: byronbyyuan Date: Sat, 12 Nov 2022 22:11:16 +0800 Subject: [PATCH] =?UTF-8?q?=20feature:=20=E5=89=8D=E7=AB=AF=E8=BF=9B?= =?UTF-8?q?=E9=98=B6=E8=AF=BE=E7=A8=8B=E5=AE=9E=E6=88=98=E8=B5=84=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Procfile" | 1 + .../README.md" | 161 ++++++++ .../VERSION" | 1 + .../api.text" | 7 + .../bin/post-compile" | 3 + .../bin/pre-compile" | 3 + .../bk.config.js" | 36 ++ .../env.js" | 37 ++ .../index-dev.html" | 36 ++ .../index.html" | 38 ++ .../mock-server/index.js" | 23 ++ .../paas-server/api/table.js" | 22 ++ .../paas-server/index.js" | 98 +++++ .../paas-server/middleware/user.js" | 44 +++ .../package.json" | 54 +++ .../postcss.config.js" | 68 ++++ .../replace-static-url-plugin.js" | 44 +++ .../src/App.vue" | 285 ++++++++++++++ .../src/api/cached-promise.js" | 64 +++ .../src/api/index.js" | 242 ++++++++++++ .../src/api/request-queue.js" | 88 +++++ .../src/common/auth.js" | 76 ++++ .../src/common/bkmagic.js" | 58 +++ .../src/common/bus.js" | 11 + .../src/common/demand-import.js" | 90 +++++ .../src/common/fully-import.js" | 11 + .../src/common/preload.js" | 26 ++ .../src/common/util.js" | 366 ++++++++++++++++++ .../src/components/auth/index.vue" | 78 ++++ .../src/components/exception/index.vue" | 85 ++++ .../src/components/scroll/index.css" | 42 ++ .../src/components/scroll/index.vue" | 129 ++++++ .../src/css/app.css" | 46 +++ .../src/css/bk-patch.css" | 15 + .../src/css/mixins/clearfix.css" | 10 + .../src/css/mixins/create-label.css" | 18 + .../src/css/mixins/ellipsis.css" | 7 + .../src/css/mixins/scroller.css" | 11 + .../src/css/reset.css" | 43 ++ .../src/css/variable.css" | 37 ++ .../src/images/403.png" | Bin 0 -> 16622 bytes .../src/images/404.png" | Bin 0 -> 17086 bytes .../src/images/500.png" | Bin 0 -> 19351 bytes .../src/images/building.png" | Bin 0 -> 13722 bytes .../src/main.js" | 68 ++++ .../src/public-path.js" | 7 + .../src/router/index.js" | 155 ++++++++ .../src/store/index.js" | 86 ++++ .../src/store/modules/bbs.js" | 26 ++ .../src/store/modules/example.js" | 21 + .../src/store/modules/vuexDemo.js" | 22 ++ .../src/views/404.vue" | 31 ++ .../src/views/custom/index.vue" | 35 ++ .../src/views/demo/detail.vue" | 70 ++++ .../src/views/demo/index.vue" | 35 ++ .../src/views/demo/list.vue" | 45 +++ .../src/views/example1/index.css" | 0 .../src/views/example1/index.vue" | 98 +++++ .../src/views/example2/index.css" | 11 + .../src/views/example2/index.vue" | 28 ++ .../src/views/index.vue" | 3 + .../src/views/vuex/index.vue" | 36 ++ .../src/views/vuex/state.vue" | 29 ++ .../static/images/favicon.png" | Bin 0 -> 1472 bytes .../static/login_success.html" | 14 + README.md | 6 + 66 files changed, 3340 insertions(+) create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/Procfile" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/README.md" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/VERSION" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/api.text" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/post-compile" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/pre-compile" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/bk.config.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/env.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/index-dev.html" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/index.html" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/mock-server/index.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/api/table.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/index.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/middleware/user.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/package.json" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/postcss.config.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/replace-static-url-plugin.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/App.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/cached-promise.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/index.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/request-queue.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/auth.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bkmagic.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bus.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/demand-import.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/fully-import.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/preload.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/util.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/auth/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/exception/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/app.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/bk-patch.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/clearfix.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/create-label.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/ellipsis.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/scroller.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/reset.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/variable.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/403.png" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/404.png" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/500.png" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/building.png" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/main.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/public-path.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/router/index.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/index.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/bbs.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/example.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/vuexDemo.js" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/404.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/custom/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/detail.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/list.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.css" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/index.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/state.vue" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/static/images/favicon.png" create mode 100644 "11_\345\211\215\347\253\257\350\277\233\351\230\266/static/login_success.html" diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/Procfile" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/Procfile" new file mode 100644 index 0000000..1a02413 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/Procfile" @@ -0,0 +1 @@ +web: npm run server diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/README.md" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/README.md" new file mode 100644 index 0000000..331cb5c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/README.md" @@ -0,0 +1,161 @@ +蓝鲸应用前端开发框架使用指南 +------------------------ + +它是基于 `Vue.js` 研发的蓝鲸体系前端分离工程的单页面应用模板(BKUI-CLI),包括了: + +- 基础工程化能力,开箱即用,无需过多配置,开发完成直接在蓝鲸PaaS可部署 +- 基础 mock 服务,帮助开发者快速伪造接口数据,测试前端 +- 蓝鲸前端/设计规范,提供统一设计及代码检测 +- bk-magic-vue 组件库,提供丰富的组件 +- 蓝鲸前端通用逻辑,包含登录模块、异步请求管理等 +- 最佳实践以及开发示例 + + + +# 本地开发 + +#### 安装依赖包 +``` +npm install +``` + +#### 配置host +配置指定域名 +``` +127.0.0.1 域名 +``` + +#### 检查配置文件 + +注意:运行之前,请检查配置文件**.babelrc**、**eslintrc.js**等以.开头的配置文件是否存在。部分操作系统会默认隐藏这类文件,导致在推送到代码仓库时漏掉,最终影响部署结果。 + +#### 启动服务 +``` +npm run dev +``` + +#### 打开链接 + +> 开发域名及端口的配置都可在`bk.config.js`中修改 + +# 前后端分离 +当前代码仅仅是应用前端,作为前后端分离架构,还需要后端服务,前后端以ajax+json进行数据处理,因此,需要 + +#### 新建后端服务模块(开发者中心) + +#### 配置APP ID +- 开发环境下编辑根目录下`env.js`,修改`development.BKPAAS_APP_ID`,用于本地开发,线上部署时会自动注入 + +#### 配置后端接口 +- 本地开发修改`env.js`中`development`的`AJAX_URL_PREFIX`字段,作为接口的url前缀 +- 线上部署测试环境修改`env.js`中`stag`方法里的的`AJAX_URL_PREFIX`字段,作为接口的url前缀 +- 线上部署测正式境修改`env.js`中`production`方法里的的`AJAX_URL_PREFIX`字段,作为接口的url前缀 + +#### 配置用户登录态信息接口,作为前端判断登录状态的验证 +> 打开首页,前端会以/user 来发起用户信息请求,如果没登录会重定向回登录页面 +> 整个框架自带登录实现,在刚打开时,如果没有登录会直接跳到登录页,如果打开后,登录过期(接口返回401状态)会弹出登录窗口 +###### 本地开发环境登录配置 +####### 用户登录配置 +`main.js` 入口会首先进行用户鉴权,如果没有用户信息,会跳转至登录页面。 +####### 登录地址配置 +本地开发需要修改根目录下`env.js` 配置 `development.BK_LOGIN_URL` 与 `development.BKPAAS_APP_ID`,线上部署会自动注入 +####### 登录鉴权 +本地登录,会在本地服务 `pass-server/middleware/user.js` 转发登录接口请求,登录接口在 `env.js` 里面的 `development.BK_LOGIN_URL` 注入 + +需要后端接口在 `/user` 路径下实现获取当前登录用户的接口, 接口规范如下 +```json +{ + "code": 0, + "data": { + "bk_username": "test", + "avatar_url": "" + }, + "message": "用户信息获取成功" +} +``` + +#### 后端服务需要解决跨域问题,推荐使用CORS方案 +详情查看MDN https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS + +# 打包构建(生成dist目录) +``` +npm run build +``` + +## 类型项目目录结构 + +使用 `bkui-cli` 初始化后,项目目录结构如下: + +```bash +|-- ROOT/ # 项目根目录 + |-- .babelrc # babel 配置 + |-- .eslintignore # eslintignore 配置 + |-- .eslintrc.js # eslint 配置 + |-- .browserslistrc # 编译代码兼容浏览器配置 + |-- env.js # 项目启动配置的环境变量等 + |-- .bk.config.js # 🌟项目编译配置文件 提供 devServe & mockServer &生产包功能集合 + |-- .gitignore # gitignore 配置 + |-- README.md # 工程的 README + |-- index-dev.html # 本地开发使用的 html + |-- index.html # 构建部署使用的 html + |-- package-lock.json # package-lock file + |-- package.json # package.json,我们在提供了基本的 doc, dev, build 等 scripts,详细内容请参见文件 + |-- postcss.config.js # postcss 配置文件,我们提供了一些常用的 postcss 插件,详细内容请参见文件 + |-- paas-server/ # 🌟可部署运行编译后代码的node服务 + | |-- middleware # 公共的中间件 + | |-- user.js # 登录中间件 + | |-- index.js # 启动服务入口 + |-- src/ # 🌟实际项目的源码目录 + | |-- App.vue # App 组件 + | |-- main.js # 主入口 + | |-- public-path.js # __webpack_public_path__ 设置 + | |-- api/ # 对 axios 封装的目录 + | | |-- cached-promise.js # promise 缓存 + | | |-- index.js # axios 封装 + | | |-- request-queue.js # 请求队列 + | |-- common/ # 项目中常用模块的目录 + | | |-- auth.js # auth + | | |-- bkmagic.js # bk-magic-vue 组件的引入 + | | |-- bus.js # 全局的 event bus + | | |-- demand-import.js # 按需引入 bk-magic-vue 的组件 + | | |-- fully-import.js # 全量引入 bk-magic-vue 的组件 + | | |-- preload.js # 页面公共请求即每次切换 router 时都必须要发送的请求 + | | |-- util.js # 项目中的常用方法 + | |-- components/ # 项目中组件的存放目录 + | | |-- auth/ # auth 组件 + | | | |-- index.css # auth 组件的样式 + | | | |-- index.vue # auth 组件 + | | |-- exception/ # exception 组件 + | | |-- index.vue # exception 组件 + | |-- css/ # 项目中通用的 css 的存放目录。各个组件的样式通常在组件各自的目录里。 + | | |-- app.css # App.vue 使用的样式 + | | |-- reset.css # 全局 reset 样式 + | | |-- variable.css # 存放 css 变量的样式 + | | |-- mixins/ # mixins 存放目录 + | | |-- scroll.css # scroll mixin + | |-- images/ # 项目中使用的图片存放目录 + | | |-- 403.png # 403 错误的图片 + | | |-- 404.png # 404 错误的图片 + | | |-- 500.png # 500 错误的图片 + | | |-- building.png # 正在建设中的图片 + | |-- router/ # 项目 router 存放目录 + | | |-- index.js # index router + | |-- store/ # 项目 store 存放目录 + | | |-- index.js # store 主模块 + | | |-- modules/ # 其他 store 模块存放目录 + | | |-- example.js # example store 模块 + | |-- views/ # 项目页面组件存放目录 + | |-- 404.vue # 404 页面组件 + | |-- index.vue # 主入口页面组件,我们在这里多使用了一层 router-view 来承载,方便之后的扩展 + | |-- example1/ # example1 页面组件存放目录 + | | |-- index.css # example1 页面组件样式 + | | |-- index.vue # example1 页面组件 + | |-- example2/ # example2 页面组件 + | | |-- index.css # example2 页面组件样式 + | | |-- index.vue # example2 页面组件 + |-- static/ # 静态资源存放目录,通常情况下, 这个目录不会人为去改变 + | |-- lib-manifest.json # webpack dll 插件生成的文件,运行 npm run dll 或者 npm run build 会自动生成 + | |-- lib.bundle.js # webpack dll 插件生成的文件,运行 npm run dll 或者 npm run build 会自动生成 + | |-- images/ # 图片静态资源存放目录 + | |-- favicon.ico # 网站 favicon +``` diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/VERSION" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/VERSION" new file mode 100644 index 0000000..524cb55 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/VERSION" @@ -0,0 +1 @@ +1.1.1 diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/api.text" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/api.text" new file mode 100644 index 0000000..9daef73 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/api.text" @@ -0,0 +1,7 @@ +/bbs/data-source/user/tableName/bbs post 创建帖子 +/bbs/data-source/user/tableName/bbs get 获取帖子 +/bbs/data-source/user/tableName/bbs/id/${detailId}` get 获取详情 + +https://magicbox.bk.tencent.com/static_api/v3/components_vue/2.0/example/index.html#/ + +https://www.npmjs.com/package/mavon-editor/v/next diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/post-compile" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/post-compile" new file mode 100644 index 0000000..d53f63d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/post-compile" @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "Post Hook: this runs after build" diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/pre-compile" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/pre-compile" new file mode 100644 index 0000000..42f2b5c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bin/pre-compile" @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "pre Hook: this runs before npm install" diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/bk.config.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bk.config.js" new file mode 100644 index 0000000..1380da5 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/bk.config.js" @@ -0,0 +1,36 @@ +const { resolve } = require('path'); +const webpack = require('webpack'); +const mockServer = require('./mock-server'); +const isModeProduction = process.env.NODE_ENV === 'production'; +const env = require('./env')(); + +module.exports = { + indexPath: isModeProduction ? './index.html' : './index-dev.html', + publicPath: env.assetsPublicPath, + devServer: { + host: env.HOST, + open: true, + port: 5000, + before: mockServer, + proxy: { + '/api/bbs': { + target: '代理地址', + changeOrigin: true, + pathRewrite: { + '^/api/bbs': '/api', + }, + }, + }, + }, + chainWebpack(config) { + config.resolve.alias + .set('vue$', 'vue/dist/vue.esm.js') + .set('@', resolve('src')) + .set('@doc', resolve('doc')); + + config.plugin('define') + .use(webpack.DefinePlugin, [env]); + config.plugin('replace').use(require('./replace-static-url-plugin'), [{}]) + .after('html'); + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/env.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/env.js" new file mode 100644 index 0000000..a0642d3 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/env.js" @@ -0,0 +1,37 @@ +const config = { + production: { + NODE_ENV: JSON.stringify('production'), + AJAX_URL_PREFIX: JSON.stringify(''), + USER_INFO_URL: JSON.stringify('/api/user'), + assetsPublicPath: '{{BK_STATIC_URL}}', + }, + development: { + NODE_ENV: JSON.stringify('development'), + AJAX_URL_PREFIX: JSON.stringify('api/'), + USER_INFO_URL: JSON.stringify('/user'), + assetsPublicPath: '/', + HOST: '', + BKPAAS_APP_ID: JSON.stringify(''), + BK_LOGIN_URL: JSON.stringify(''), + }, + stag: { + NODE_ENV: JSON.stringify('production'), + AJAX_URL_PREFIX: JSON.stringify(''), + USER_INFO_URL: JSON.stringify('/api/user'), + assetsPublicPath: '{{BK_STATIC_URL}}', + }, +}; + +module.exports = () => { + if (process.env.BK_NODE_ENV === 'development') { + return config.development; + } + + const BUILD_ENV = process.env.BKPAAS_ENVIRONMENT || process.env.BK_NODE_ENV; + + if (BUILD_ENV === 'stag') { + return config.stag; + } + return config.production; +}; + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/index-dev.html" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/index-dev.html" new file mode 100644 index 0000000..2508a83 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/index-dev.html" @@ -0,0 +1,36 @@ + + + + + + + index + + +
+ + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/index.html" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/index.html" new file mode 100644 index 0000000..39a7666 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/index.html" @@ -0,0 +1,38 @@ + + + + + + + index + + +
+ + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/mock-server/index.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/mock-server/index.js" new file mode 100644 index 0000000..c4f6b74 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/mock-server/index.js" @@ -0,0 +1,23 @@ +module.exports = (app) => { + app.use(require('cookie-parser')()); + /** mock 接口 */ + app.get('/api/table', (req, res) => { + res.json({ + code: 0, + message: '', + data: { + list: [ + { + id: 1, + ip: '127.0.0.1', + source: '微信', + status: '正常', + create_time: '2018-05-25 15:02:24', + children: [], + }, + ], + }, + }); + }); + app.use(require('../paas-server/middleware/user')); +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/api/table.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/api/table.js" new file mode 100644 index 0000000..c70293b --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/api/table.js" @@ -0,0 +1,22 @@ +const tableList = (app) => { + app.get('/api/table', (req, res) => { + res.json({ + code: 0, + message: '', + data: { + list: [ + { + id: 1, + ip: '127.0.0.1', + source: '微信', + status: '正常', + create_time: '2018-05-25 15:02:24', + children: [], + }, + ], + }, + }); + }); +}; + +module.exports = tableList; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/index.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/index.js" new file mode 100644 index 0000000..ba7cdc1 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/index.js" @@ -0,0 +1,98 @@ +/** + * @file prod server + * 静态资源 + * 模块渲染输出 + * 注入全局变量 + * 添加html模板引擎 + */ +const Express = require('express'); +const path = require('path'); +const artTemplate = require('express-art-template'); +const cookieParser = require('cookie-parser'); +const history = require('connect-history-api-fallback'); +const user = require('./middleware/user'); + +const mockTable = require('./api/table'); + +const app = new Express(); + +const PORT = process.env.PORT || 5000; + +/** 仅解决空模版直接部署时,模拟的接口,防止直接部署接口404,实际项目可删除 */ +mockTable(app); + +app.use(cookieParser()); +app.use(user); + +// 注入全局变量 +const GLOBAL_VAR = { + SITE_URL: process.env.SITE_URL || '', + BK_STATIC_URL: process.env.BK_STATIC_URL || '', + // 当前应用的环境,预发布环境为 stag,正式环境为 prod + BKPAAS_ENVIRONMENT: process.env.BKPAAS_ENVIRONMENT || '', + // EngineApp名称,拼接规则:bkapp-{appcode}-{BKPAAS_ENVIRONMENT} + BKPAAS_ENGINE_APP_NAME: process.env.BKPAAS_ENGINE_APP_NAME || '', + // 内部版对应ieod,外部版对应tencent,混合云版对应clouds + BKPAAS_ENGINE_REGION: process.env.BKPAAS_ENGINE_REGION || '', + // APP CODE + BKPAAS_APP_ID: process.env.BKPAAS_APP_ID || '', + BKPAAS_APP_SECRET: process.env.BKPAAS_APP_SECRET || '', + BK_LOGIN_URL: process.env.BK_LOGIN_URL || '', +}; + + +const distDir = path.resolve(__dirname, '../dist'); + +app.use(history({ + index: '/', + rewrites: [ + { + // connect-history-api-fallback 默认会对 url 中有 . 的 url 当成静态资源处理而不是当成页面地址来处理 + // 兼容 /cs/28aa9eda67644a6eb254d694d944307e/cluster/BCS-MESOS-10001/node/10.121.23.12 这样以 IP 结尾的 url + // from: /\d+\.\d+\.\d+\.\d+$/, + from: /\/(\d+\.)*\d+$/, + to: '/', + }, + { + // connect-history-api-fallback 默认会对 url 中有 . 的 url 当成静态资源处理而不是当成页面地址来处理 + // 兼容 下面带有.路径 + // /bcs/projectId/app/214/taskgroups/0.application-1-13.test123.10013.1510806131114508229/containers/containerId + from: /\/\/+.*\..*\//, + to: '/', + }, + ], +})); + + +// 首页 +app.get('/', (req, res) => { + const scriptName = (req.headers['x-script-name'] || '').replace(/\//g, ''); + // 使用子路径 + if (scriptName) { + GLOBAL_VAR.BK_STATIC_URL = `/${scriptName}`; + GLOBAL_VAR.SITE_URL = `/${scriptName}`; + } else { + // 使用系统分配域名 + GLOBAL_VAR.BK_STATIC_URL = ''; + GLOBAL_VAR.SITE_URL = ''; + } + // 注入全局变量 + res.render(path.join(distDir, 'index.html'), GLOBAL_VAR); +}); + + +app.use('/static', Express.static(path.join(distDir, '../dist/static'))); +// 配置视图 +app.set('views', path.join(__dirname, '../dist')); + +// 配置模板引擎 +// http://aui.github.io/art-template/zh-cn/docs/ +app.engine('html', artTemplate); +app.set('view engine', 'html'); + +// 配置端口 +app.listen(PORT, () => { + console.log(`App is running in port ${PORT}`); +}); + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/middleware/user.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/middleware/user.js" new file mode 100644 index 0000000..7ca8a35 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/paas-server/middleware/user.js" @@ -0,0 +1,44 @@ + +const env = require('../../env')(); +// 获取登录用户详情 +module.exports = async function user(req, res, next) { + if (req.path !== '/api/user' && req.path !== '/user') { + next(); + return; + } + const request = require('request'); + const loginUrl = process.env.BK_LOGIN_URL || (env.BK_LOGIN_URL && JSON.parse(env.BK_LOGIN_URL)) || ''; + const url = `${loginUrl}api/v3/is_login/?bk_token=${req.cookies.bk_token}`; + request(url, (err, response, body) => { + if (err) { + res.status(500); + res.send(''); + return; + } + const data = JSON.parse(body || '{}'); + + // 有登录状态 + if (data.code === 0) { + const { + bk_username, + avatar_url, + } = data.data; + res.json({ + code: 0, + message: data.msg, + data: { + username: bk_username, + avatar_url, + }, + }); + return; + } + // 登录状态失效 + // const loginURL = `${BK_LOGIN_URL}plain?`; + res.status(401); + // res.set({ + // 'X-Login-Url': loginURL, + // }); + res.send(''); + }); +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/package.json" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/package.json" new file mode 100644 index 0000000..51d7e48 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/package.json" @@ -0,0 +1,54 @@ +{ + "name": "node-vue-spa", + "version": "1.0.0", + "description": "Project description", + "author": "", + "scripts": { + "dev": "bk-cli-service dev", + "build": "bk-cli-service build", + "build:stag": "bk-cli-service build --mode=stag", + "server": "node ./paas-server/index.js", + "lint": "eslint --ext .js,.vue ./src", + "lint-fix": "eslint --fix --ext .js,.vue ./src" + }, + "rules": { + "no-unused-vars": 0 + }, + "keywords": [], + "license": "ISC", + "dependencies": { + "art-template": "^4.13.2", + "axios": "^0.21.1", + "babel-plugin-import-bk-magic-vue": "latest", + "bk-magic-vue": "latest", + "connect-history-api-fallback": "^1.6.0", + "cookie-parser": "^1.4.5", + "express": "^4.17.1", + "express-art-template": "^1.0.1", + "mavon-editor": "^2.10.4", + "postcss": "^8.3.0", + "postcss-advanced-variables": "~3.0.0", + "postcss-atroot": "~0.1.3", + "postcss-extend-rule": "~2.0.0", + "postcss-import": "~12.0.1", + "postcss-import-webpack-resolver": "~1.0.1", + "postcss-loader": "~3.0.0", + "postcss-mixins": "~6.2.1", + "postcss-nested": "~4.1.2", + "postcss-preset-env": "~6.6.0", + "postcss-property-lookup": "~2.0.0", + "postcss-url": "~8.0.0", + "request": "^2.88.2", + "vue-router": "~3.0.6", + "vuex": "~3.1.1" + }, + "devDependencies": { + "@blueking/babel-preset-bk": "^2.0.0", + "@blueking/cli-service-webpack4": "^2.0.0", + "@blueking/eslint-config-bk": "^2.0.0" + }, + "engines": { + "node": ">= 12.16.3", + "npm": ">= 6.4.1 <7" + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/postcss.config.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/postcss.config.js" new file mode 100644 index 0000000..f608894 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/postcss.config.js" @@ -0,0 +1,68 @@ +/** + * @file postcss 基本配置 + * @author + */ + +const createResolver = require('postcss-import-webpack-resolver'); +// const baseConf = require('./build/webpack.base.conf.js') +const { resolve } = require('path'); + +// https://github.com/michael-ciniawsky/postcss-load-config +module.exports = { + plugins: { + // 把 import 的内容转换为 inline + // @see https://github.com/postcss/postcss-import#postcss-import + 'postcss-import': { + // 使用 webpack 配置里的 resolve.alias + // https://github.com/krambuhl/postcss-import-webpack-resolver#postcss-import-webpack-resolver + resolve: createResolver({ + alias: { + '@': resolve('src'), + '@doc': resolve('doc'), + }, + modules: ['src', 'node_modules'], + }), + }, + + // mixins,本插件需要放在 postcss-simple-vars 和 postcss-nested 插件前面 + // @see https://github.com/postcss/postcss-mixins#postcss-mixins- + 'postcss-mixins': { + }, + + // 用于在 URL ( )上重新定位、内嵌或复制。 + // @see https://github.com/postcss/postcss-url#postcss-url + 'postcss-url': { + url: 'rebase', + }, + + // cssnext 已经不再维护,推荐使用 postcss-preset-env + 'postcss-preset-env': { + // see https://github.com/csstools/postcss-preset-env#options + stage: 0, + autoprefixer: { + grid: true, + }, + }, + // 这个插件可以在写 nested 样式时省略开头的 & + // @see https://github.com/postcss/postcss-nested#postcss-nested- + 'postcss-nested': {}, + + // 将 @at-root 里的规则放入到根节点 + // @see https://github.com/OEvgeny/postcss-atroot#postcss-at-root- + 'postcss-atroot': {}, + + // 提供 @extend 语法 + // @see https://github.com/jonathantneal/postcss-extend-rule#postcss-extend-rule- + 'postcss-extend-rule': {}, + + // 变量相关 + // @see https://github.com/jonathantneal/postcss-advanced-variables#postcss-advanced-variables- + 'postcss-advanced-variables': { + // variables 属性内的变量为全局变量 + }, + + // 类似于 stylus,直接引用属性而不需要变量定义 + // @see https://github.com/simonsmith/postcss-property-lookup#postcss-property-lookup- + 'postcss-property-lookup': {}, + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/replace-static-url-plugin.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/replace-static-url-plugin.js" new file mode 100644 index 0000000..c084357 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/replace-static-url-plugin.js" @@ -0,0 +1,44 @@ +/** + * @file 替换 asset css 中的 BK_STATIC_URL,__webpack_public_path__ 没法解决 asset 里静态资源的 url + * @author + */ + +const { extname } = require('path'); + +class ReplaceCSSStaticUrlPlugin { + apply(compiler) { + // emit: 在生成资源并输出到目录之前 + compiler.hooks.emit.tapAsync('ReplaceCSSStaticUrlPlugin', (compilation, callback) => { + const assets = Object.keys(compilation.assets); + const assetsLen = assets.length; + for (let i = 0; i < assetsLen; i++) { + const fileName = assets[i]; + if (extname(fileName) !== '.css') { + continue; + } + + const asset = compilation.assets[fileName]; + + const minifyFileContent = asset.source().toString() + .replace( + /\{\{\s*BK_STATIC_URL\s*\}\}/g, + () => '../../', + ); + // 设置输出资源 + compilation.assets[fileName] = { + // 返回文件内容 + source: () => minifyFileContent, + // 返回文件大小 + size: () => Buffer.byteLength(minifyFileContent, 'utf8'), + }; + } + + callback(); + }); + } +} + + +module.exports = ReplaceCSSStaticUrlPlugin; + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/App.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/App.vue" new file mode 100644 index 0000000..d774c20 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/App.vue" @@ -0,0 +1,285 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/cached-promise.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/cached-promise.js" new file mode 100644 index 0000000..e5a07ad --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/cached-promise.js" @@ -0,0 +1,64 @@ +/** + * @file promise 缓存 + * @author + */ + +export default class CachedPromise { + constructor() { + this.cache = {}; + } + + /** + * 根据 id 获取缓存对象,如果不传 id,则获取所有缓存 + * + * @param {string?} id id + * + * @return {Array|Promise} 缓存集合或promise 缓存对象 + */ + get(id) { + if (typeof id === 'undefined') { + return Object.keys(this.cache).map(requestId => this.cache[requestId]); + } + return this.cache[id]; + } + + /** + * 设置 promise 缓存对象 + * + * @param {string} id id + * @param {Promise} promise 要缓存的 promise 对象 + * + * @return {Promise} promise 对象 + */ + set(id, promise) { + Object.assign(this.cache, { [id]: promise }); + } + + /** + * 删除 promise 缓存对象 + * + * @param {string|Array?} deleteIds 要删除的缓存对象的 id,如果不传则删除所有 + * + * @return {Promise} 以成功的状态返回 Promise 对象 + */ + delete(deleteIds) { + let requestIds = []; + if (typeof deleteIds === 'undefined') { + requestIds = Object.keys(this.cache); + } else if (deleteIds instanceof Array) { + deleteIds.forEach((id) => { + if (this.get(id)) { + requestIds.push(id); + } + }); + } else if (this.get(deleteIds)) { + requestIds.push(deleteIds); + } + + requestIds.forEach((requestId) => { + delete this.cache[requestId]; + }); + + return Promise.resolve(deleteIds); + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/index.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/index.js" new file mode 100644 index 0000000..4fa04ec --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/index.js" @@ -0,0 +1,242 @@ +/** + * @file axios 封装 + * @author + */ + +/* eslint-disable */ + +import Vue from 'vue'; +import axios from 'axios'; +import cookie from 'cookie'; + +import CachedPromise from './cached-promise'; +import RequestQueue from './request-queue'; +import { bus } from '../common/bus'; +import { messageError } from '@/common/bkmagic'; +import UrlParse from 'url-parse'; +import queryString from 'query-string'; + +// axios 实例 +const axiosInstance = axios.create({ + baseURL: window.PROJECT_CONFIG.SITE_URL + AJAX_URL_PREFIX, + withCredentials: true, + headers: { 'X-REQUESTED-WITH': 'XMLHttpRequest' }, +}); + +/** + * request interceptor + */ +axiosInstance.interceptors.request.use((config) => { + const urlObj = new UrlParse(config.url); + const query = queryString.parse(urlObj.query); + // 在发起请求前,注入CSRFToken,解决跨域 + injectCSRFTokenToHeaders(); + return config; +}, error => Promise.reject(error)); + +/** + * response interceptor + */ +axiosInstance.interceptors.response.use( + response => response.data, + error => Promise.reject(error), +); + +const http = { + queue: new RequestQueue(), + cache: new CachedPromise(), + cancelRequest: requestId => http.queue.cancel(requestId), + cancelCache: requestId => http.cache.delete(requestId), + cancel: requestId => Promise.all([http.cancelRequest(requestId), http.cancelCache(requestId)]), +}; + +const methodsWithoutData = ['delete', 'get', 'head', 'options']; +const methodsWithData = ['post', 'put', 'patch']; +const allMethods = [...methodsWithoutData, ...methodsWithData]; + +// 在自定义对象 http 上添加各请求方法 +allMethods.forEach((method) => { + Object.defineProperty(http, method, { + get () { + return getRequest(method); + }, + }); +}); + +/** + * 获取 http 不同请求方式对应的函数 + * + * @param {string} method http method 与 axios 实例中的 method 保持一致 + * + * @return {Function} 实际调用的请求函数 + */ +function getRequest (method) { + if (methodsWithData.includes(method)) { + return (url, data, config) => getPromise(method, url, data, config); + } + return (url, config) => getPromise(method, url, null, config); +} + +/** + * 实际发起 http 请求的函数,根据配置调用缓存的 promise 或者发起新的请求 + * + * @param {string} method http method 与 axios 实例中的 method 保持一致 + * @param {string} url 请求地址 + * @param {Object} data 需要传递的数据, 仅 post/put/patch 三种请求方式可用 + * @param {Object} userConfig 用户配置,包含 axios 的配置与本系统自定义配置 + * + * @return {Promise} 本次http请求的Promise + */ +async function getPromise (method, url, data, userConfig = {}) { + const config = initConfig(method, url, userConfig); + let promise; + if (config.cancelPrevious) { + await http.cancel(config.requestId); + } + + if (config.clearCache) { + http.cache.delete(config.requestId); + } else { + promise = http.cache.get(config.requestId); + } + if (config.fromCache && promise) { + return promise; + } + + promise = new Promise(async (resolve, reject) => { + const axiosRequest = methodsWithData.includes(method) + ? axiosInstance[method](url, data, config) + : axiosInstance[method](url, config); + + try { + const response = await axiosRequest; + Object.assign(config, response.config || {}); + handleResponse({ config, response, resolve, reject }); + } catch (error) { + Object.assign(config, error.config); + reject(error); + } + }).catch(error => handleReject(error, config)) + .finally(() => { + // console.log('finally', config) + }); + + // 添加请求队列 + http.queue.set(config); + // 添加请求缓存 + http.cache.set(config.requestId, promise); + + return promise; +} + +/** + * 处理 http 请求成功结果 + * + * @param {Object} 请求配置 + * @param {Object} response cgi 原始返回数据 + * @param {Function} response, promise 完成函数 + * @param {Function} reject promise 拒绝函数 + */ +function handleResponse ({ config, response, resolve, reject }) { + if (!response.data && config.globalError) { + reject({ message: response.message }); + } else { + resolve(config.originalResponse ? response : response.data, config); + } + http.queue.delete(config.requestId); +} + +/** + * 处理 http 请求失败结果 + * + * @param {Object} error Error 对象 + * @param {config} config 请求配置 + * + * @return {Promise} promise 对象 + */ +function handleReject (error, config) { + if (axios.isCancel(error)) { + return Promise.reject(error); + } + + http.queue.delete(config.requestId); + if (config.globalError && error.response) { + const { status, data } = error.response; + const nextError = { message: error.message, response: error.response }; + if (status === 401) { + bus.$emit('show-login-modal', nextError.response); + } else if (status === 500) { + nextError.message = '系统出现异常'; + } else if (data && data.message) { + nextError.message = data.message; + } + messageError(nextError.message); + console.error(nextError.message); + return Promise.reject(nextError); + } + messageError(error.message); + console.error(error.message); + return Promise.reject(error); +} + +/** + * 初始化本系统 http 请求的各项配置 + * + * @param {string} method http method 与 axios 实例中的 method 保持一致 + * @param {string} url 请求地址, 结合 method 生成 requestId + * @param {Object} userConfig 用户配置,包含 axios 的配置与本系统自定义配置 + * + * @return {Promise} 本次 http 请求的 Promise + */ +function initConfig (method, url, userConfig) { + const defaultConfig = { + ...getCancelToken(), + // http 请求默认 id + requestId: `${method}_${url}`, + // 是否全局捕获异常 + globalError: true, + // 是否直接复用缓存的请求 + fromCache: false, + // 是否在请求发起前清楚缓存 + clearCache: false, + // 响应结果是否返回原始数据 + originalResponse: true, + // 当路由变更时取消请求 + cancelWhenRouteChange: true, + // 取消上次请求 + cancelPrevious: true, + }; + return Object.assign(defaultConfig, userConfig); +} + +/** + * 生成 http 请求的 cancelToken,用于取消尚未完成的请求 + * + * @return {Object} {cancelToken: axios 实例使用的 cancelToken, cancelExcutor: 取消http请求的可执行函数} + */ +function getCancelToken () { + let cancelExcutor; + const cancelToken = new axios.CancelToken((excutor) => { + cancelExcutor = excutor; + }); + return { + cancelToken, + cancelExcutor, + }; +} + +Vue.prototype.$http = http; + +export default http; + +/** + * 向 http header 注入 CSRFToken,CSRFToken key 值与后端一起协商制定 + */ +export function injectCSRFTokenToHeaders () { + const CSRFToken = cookie.parse(document.cookie)[`${window.PROJECT_CONFIG.BKPAAS_APP_ID}_csrftoken`]; + if (CSRFToken !== undefined) { + axiosInstance.defaults.headers.common['X-CSRFToken'] = CSRFToken; + } else { + console.warn('Can not find csrftoken in document.cookie'); + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/request-queue.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/request-queue.js" new file mode 100644 index 0000000..64f5466 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/api/request-queue.js" @@ -0,0 +1,88 @@ +/** + * @file 请求队列 + * @author + */ + +export default class RequestQueue { + constructor() { + this.queue = []; + } + + /** + * 根据 id 获取请求对象,如果不传入 id,则获取整个地列 + * + * @param {string?} id id + * + * @return {Array|Object} 队列集合或队列对象 + */ + get(id) { + if (typeof id === 'undefined') { + return this.queue; + } + return this.queue.filter(request => request.requestId === id); + } + + /** + * 设置新的请求对象到请求队列中 + * + * @param {Object} newRequest 请求对象 + */ + set(newRequest) { + this.queue.push(newRequest); + // if (!this.queue.some(request => request.requestId === newRequest.requestId)) { + // this.queue.push(newRequest) + // } + } + + /** + * 根据 id 删除请求对象 + * + * @param {string} id id + */ + delete(id) { + // const target = this.queue.filter(request => request.requestId === id)[0] + // if (target) { + // const index = this.queue.indexOf(target) + // this.queue.splice(index, 1) + // } + this.queue = [...this.queue.filter(request => request.requestId !== id)]; + } + + /** + * cancel 请求队列中的请求 + * + * @param {string|Array?} requestIds 要 cancel 的请求 id,如果不传,则 cancel 所有请求 + * @param {string?} msg cancel 时的信息 + * + * @return {Promise} promise 对象 + */ + cancel(requestIds, msg = 'request canceled') { + let cancelQueue = []; + if (typeof requestIds === 'undefined') { + cancelQueue = [...this.queue]; + } else if (requestIds instanceof Array) { + requestIds.forEach((requestId) => { + const cancelRequest = this.get(requestId); + if (cancelRequest) { + cancelQueue = [...cancelQueue, ...cancelRequest]; + } + }); + } else { + const cancelRequest = this.get(requestIds); + if (cancelRequest) { + cancelQueue = [...cancelQueue, ...cancelRequest]; + } + } + + try { + cancelQueue.forEach((request) => { + const { requestId } = request; + this.delete(requestId); + request.cancelExcutor(`${msg}: ${requestId}`); + }); + return Promise.resolve(requestIds); + } catch (error) { + return Promise.reject(error); + } + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/auth.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/auth.js" new file mode 100644 index 0000000..3c46e8c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/auth.js" @@ -0,0 +1,76 @@ +/** + * @file auth + * @author + */ + +import store from '@/store'; + +const ANONYMOUS_USER = { + id: null, + isAuthenticated: false, + username: 'anonymous', +}; + +let currentUser = { + id: '', + username: '', +}; + +export default { + /** + * 未登录状态码 + */ + HTTP_STATUS_UNAUTHORIZED: 401, + + /** + * 获取当前用户 + * + * @return {Object} 当前用户信息 + */ + getCurrentUser() { + return currentUser; + }, + + redirectToLogin() { + // const callbackUrl = `${location.origin}/static/login_success.html?is_ajax=1`; + const callbackUrl = encodeURIComponent(window.location.href); + const appCode = window.PROJECT_CONFIG.BKPAAS_APP_ID || BKPAAS_APP_ID; + const loginUrl = window.PROJECT_CONFIG.BK_LOGIN_URL || BK_LOGIN_URL; + window.location.href = `${loginUrl}?app_id=${appCode}&c_url=${callbackUrl}`; + }, + + /** + * 请求当前用户信息 + * + * @return {Promise} promise 对象 + */ + requestCurrentUser() { + let promise = null; + if (currentUser.isAuthenticated) { + promise = new Promise((resolve) => { + resolve(currentUser); + }); + } else { + if (!store.state.user || !Object.keys(store.state.user).length) { + // store action userInfo 里,如果请求成功会更新 state.user + const req = store.dispatch('userInfo'); + promise = new Promise((resolve, reject) => { + req.then(() => { + // 存储当前用户信息(全局) + currentUser = store.getters.user; + currentUser.isAuthenticated = true; + resolve(currentUser); + }, (err) => { + if (err.response.status === this.HTTP_STATUS_UNAUTHORIZED || err.crossDomain) { + resolve({ ...ANONYMOUS_USER }); + } else { + reject(err); + } + }); + }); + } + } + + return promise; + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bkmagic.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bkmagic.js" new file mode 100644 index 0000000..690e088 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bkmagic.js" @@ -0,0 +1,58 @@ +/** + * @file 引入 bk-magic-vue 组件 + * @author + */ + +import Vue from 'vue'; + +// 全量引入 +import './fully-import'; + +// 按需引入 +// import './demand-import' + +const Message = Vue.prototype.$bkMessage; + +let messageInstance = null; + +export const messageError = (message, delay = 3000) => { + messageInstance?.close(); + messageInstance = Message({ + message, + delay, + theme: 'error', + }); +}; + +export const messageSuccess = (message, delay = 3000) => { + messageInstance?.close(); + messageInstance = Message({ + message, + delay, + theme: 'success', + }); +}; + +export const messageInfo = (message, delay = 3000) => { + messageInstance?.close(); + messageInstance = Message({ + message, + delay, + theme: 'primary', + }); +}; + +export const messageWarn = (message, delay = 3000) => { + messageInstance?.close(); + messageInstance = Message({ + message, + delay, + theme: 'warning', + hasCloseIcon: true, + }); +}; + +Vue.prototype.messageError = messageError; +Vue.prototype.messageSuccess = messageSuccess; +Vue.prototype.messageInfo = messageInfo; +Vue.prototype.messageWarn = messageWarn; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bus.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bus.js" new file mode 100644 index 0000000..8dd557b --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/bus.js" @@ -0,0 +1,11 @@ + +/** + * @file event bus + * @author + */ + +import Vue from 'vue'; + +// Use a bus for components communication, +// see https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication +export const bus = new Vue(); diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/demand-import.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/demand-import.js" new file mode 100644 index 0000000..e313068 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/demand-import.js" @@ -0,0 +1,90 @@ +/** + * @file 按需引入 bk-magic-vue 组件 + * @author + */ + +/* eslint-disable import/no-duplicates */ + +import Vue from 'vue'; + +import { + bkBadge, bkButton, bkCheckbox, bkCheckboxGroup, bkCol, bkCollapse, bkCollapseItem, bkContainer, bkDatePicker, + bkDialog, bkDropdownMenu, bkException, bkForm, + bkFormItem, bkInfoBox, bkInput, bkLoading, bkMemberSelector, bkMessage, + bkNavigation, bkNavigationMenu, bkNavigationMenuItem, bkNotify, bkOption, bkOptionGroup, bkPagination, + bkPopover, bkProcess, bkProgress, bkRadio, bkRadioGroup, bkRoundProgress, bkRow, bkSearchSelect, bkSelect, + bkSideslider, bkSlider, bkSteps, bkSwitcher, bkTab, bkTabPanel, bkTable, bkTableColumn, bkTagInput, bkPopconfirm, + bkTimePicker, bkTimeline, bkTransfer, bkTree, + bkUpload, bkClickoutside, bkTooltips, bkSwiper, bkRate, bkAnimateNumber, + bkVirtualScroll, bkColorPicker, bkCard, +} from '@tencent/bk-magic-vue'; + +// bkDiff 组件体积较大且不是很常用,因此注释掉。如果需要,打开注释即可 +// import { bkDiff } from '@tencent/bk-magic-vue' + +// components use +Vue.use(bkBadge); +Vue.use(bkButton); +Vue.use(bkCheckbox); +Vue.use(bkCheckboxGroup); +Vue.use(bkCol); +Vue.use(bkCollapse); +Vue.use(bkCollapseItem); +Vue.use(bkContainer); +Vue.use(bkDatePicker); +Vue.use(bkDialog); +Vue.use(bkDropdownMenu); +Vue.use(bkException); +Vue.use(bkForm); +Vue.use(bkFormItem); +Vue.use(bkInput); +Vue.use(bkMemberSelector); +Vue.use(bkNavigation); +Vue.use(bkNavigationMenu); +Vue.use(bkNavigationMenuItem); +Vue.use(bkOption); +Vue.use(bkOptionGroup); +Vue.use(bkPagination); +Vue.use(bkPopover); +Vue.use(bkProcess); +Vue.use(bkProgress); +Vue.use(bkRadio); +Vue.use(bkRadioGroup); +Vue.use(bkRoundProgress); +Vue.use(bkRow); +Vue.use(bkSearchSelect); +Vue.use(bkSelect); +Vue.use(bkSideslider); +Vue.use(bkSlider); +Vue.use(bkSteps); +Vue.use(bkSwitcher); +Vue.use(bkTab); +Vue.use(bkTabPanel); +Vue.use(bkTable); +Vue.use(bkTableColumn); +Vue.use(bkTagInput); +Vue.use(bkPopconfirm); +Vue.use(bkTimePicker); +Vue.use(bkTimeline); +Vue.use(bkTransfer); +Vue.use(bkTree); +Vue.use(bkUpload); +Vue.use(bkSwiper); +Vue.use(bkRate); +Vue.use(bkAnimateNumber); +Vue.use(bkVirtualScroll); +Vue.use(bkColorPicker); +Vue.use(bkCard); + +// bkDiff 组件体积较大且不是很常用,因此注释了。如果需要,打开注释即可 +// Vue.use(bkDiff) + +// directives use +Vue.use(bkClickoutside); +Vue.use(bkTooltips); +Vue.use(bkLoading); + +// Vue prototype mount +Vue.prototype.$bkInfo = bkInfoBox; +Vue.prototype.$bkMessage = bkMessage; +Vue.prototype.$bkNotify = bkNotify; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/fully-import.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/fully-import.js" new file mode 100644 index 0000000..1fa2e3e --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/fully-import.js" @@ -0,0 +1,11 @@ +/** + * @file 全量引入 bk-magic-vue 组件 + * @author bk + */ + +import Vue from 'vue'; +import bkMagicVue from 'bk-magic-vue'; +import 'bk-magic-vue/dist/bk-magic-vue.min.css'; + +Vue.use(bkMagicVue); + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/preload.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/preload.js" new file mode 100644 index 0000000..2eba092 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/preload.js" @@ -0,0 +1,26 @@ +/** + * @file 页面公共请求即每切换 router 时都必须要发送的请求 + * @author + */ + +// import store from '@/store' + +// const config = { +// fromCache: true, +// cancelWhenRouteChange: false +// } + +/** + * 获取 user 信息 + * + * @return {Promise} promise 对象 + */ +// function getUser () { +// return store.dispatch('userInfo', config) +// } + +export default function () { + return Promise.all([ + // getUser() + ]); +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/util.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/util.js" new file mode 100644 index 0000000..c9fbbc5 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/common/util.js" @@ -0,0 +1,366 @@ +/** + * @file 通用方法 + * @author + */ + +/* eslint-disable */ + +/** + * 函数柯里化 + * + * @example + * function add (a, b) {return a + b} + * curry(add)(1)(2) + * + * @param {Function} fn 要柯里化的函数 + * + * @return {Function} 柯里化后的函数 + */ +export function curry (fn) { + const judge = (...args) => args.length === fn.length + ? fn(...args) + : arg => judge(...args, arg); + return judge; +} + +/** + * 判断是否是对象 + * + * @param {Object} obj 待判断的 + * + * @return {boolean} 判断结果 + */ +export function isObject (obj) { + return obj !== null && typeof obj === 'object'; +} + +/** + * 规范化参数 + * + * @param {Object|string} type vuex type + * @param {Object} payload vuex payload + * @param {Object} options vuex options + * + * @return {Object} 规范化后的参数 + */ +export function unifyObjectStyle (type, payload, options) { + if (isObject(type) && type.type) { + options = payload; + payload = type; + type = type.type; + } + + if (process.env.NODE_ENV !== 'production') { + if (typeof type !== 'string') { + console.warn(`expects string as the type, but found ${typeof type}.`); + } + } + + return { type, payload, options }; +} + +/** + * 以 baseColor 为基础生成随机颜色 + * + * @param {string} baseColor 基础颜色 + * @param {number} count 随机颜色个数 + * + * @return {Array} 颜色数组 + */ +export function randomColor (baseColor, count) { + const segments = baseColor.match(/[\da-z]{2}/g); + // 转换成 rgb 数字 + for (let i = 0; i < segments.length; i++) { + segments[i] = parseInt(segments[i], 16); + } + const ret = []; + // 生成 count 组颜色,色差 20 * Math.random + for (let i = 0; i < count; i++) { + ret[i] = `#${ + Math.floor(segments[0] + (Math.random() < 0.5 ? -1 : 1) * Math.random() * 20).toString(16) + }${Math.floor(segments[1] + (Math.random() < 0.5 ? -1 : 1) * Math.random() * 20).toString(16) + }${Math.floor(segments[2] + (Math.random() < 0.5 ? -1 : 1) * Math.random() * 20).toString(16)}`; + } + return ret; +} + +/** + * min max 之间的随机整数 + * + * @param {number} min 最小值 + * @param {number} max 最大值 + * + * @return {number} 随机数 + */ +export function randomInt (min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +/** + * 异常处理 + * + * @param {Object} err 错误对象 + * @param {Object} ctx 上下文对象,这里主要指当前的 Vue 组件 + */ +export function catchErrorHandler (err, ctx) { + const { data } = err; + if (data) { + if (!data.code || data.code === 404) { + ctx.exceptionCode = { + code: '404', + msg: '当前访问的页面不存在', + }; + } else if (data.code === 403) { + ctx.exceptionCode = { + code: '403', + msg: 'Sorry,您的权限不足!', + }; + } else { + console.error(err); + ctx.bkMessageInstance = ctx.$bkMessage({ + theme: 'error', + message: err.message || err.data.msg || err.statusText, + }); + } + } else { + console.error(err); + ctx.bkMessageInstance = ctx.$bkMessage({ + theme: 'error', + message: err.message || err.data.msg || err.statusText, + }); + } +} + +/** + * 获取字符串长度,中文算两个,英文算一个 + * + * @param {string} str 字符串 + * + * @return {number} 结果 + */ +export function getStringLen (str) { + let len = 0; + for (let i = 0; i < str.length; i++) { + if (str.charCodeAt(i) > 127 || str.charCodeAt(i) === 94) { + len += 2; + } else { + len++; + } + } + return len; +} + +/** + * 转义特殊字符 + * + * @param {string} str 待转义字符串 + * + * @return {string} 结果 + */ +export const escape = str => String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); + +/** + * 对象转为 url query 字符串 + * + * @param {*} param 要转的参数 + * @param {string} key key + * + * @return {string} url query 字符串 + */ +export function json2Query (param, key) { + const mappingOperator = '='; + const separator = '&'; + let paramStr = ''; + + if (param instanceof String || typeof param === 'string' + || param instanceof Number || typeof param === 'number' + || param instanceof Boolean || typeof param === 'boolean' + ) { + paramStr += separator + key + mappingOperator + encodeURIComponent(param); + } else { + Object.keys(param).forEach((p) => { + const value = param[p]; + const k = (key === null || key === '' || key === undefined) + ? p + : key + (param instanceof Array ? `[${p}]` : `.${p}`); + paramStr += separator + json2Query(value, k); + }); + } + return paramStr.substr(1); +} + +/** + * 字符串转换为驼峰写法 + * + * @param {string} str 待转换字符串 + * + * @return {string} 转换后字符串 + */ +export function camelize (str) { + return str.replace(/-(\w)/g, (strMatch, p1) => p1.toUpperCase()); +} + +/** + * 获取元素的样式 + * + * @param {Object} elem dom 元素 + * @param {string} prop 样式属性 + * + * @return {string} 样式值 + */ +export function getStyle (elem, prop) { + if (!elem || !prop) { + return false; + } + + // 先获取是否有内联样式 + let value = elem.style[camelize(prop)]; + + if (!value) { + // 获取的所有计算样式 + let css = ''; + if (document.defaultView && document.defaultView.getComputedStyle) { + css = document.defaultView.getComputedStyle(elem, null); + value = css ? css.getPropertyValue(prop) : null; + } + } + + return String(value); +} + +/** + * 获取元素相对于页面的高度 + * + * @param {Object} node 指定的 DOM 元素 + */ +export function getActualTop (node) { + let actualTop = node.offsetTop; + let current = node.offsetParent; + + while (current !== null) { + actualTop += current.offsetTop; + current = current.offsetParent; + } + + return actualTop; +} + +/** + * 获取元素相对于页面左侧的宽度 + * + * @param {Object} node 指定的 DOM 元素 + */ +export function getActualLeft (node) { + let actualLeft = node.offsetLeft; + let current = node.offsetParent; + + while (current !== null) { + actualLeft += current.offsetLeft; + current = current.offsetParent; + } + + return actualLeft; +} + +/** + * document 总高度 + * + * @return {number} 总高度 + */ +export function getScrollHeight () { + let scrollHeight = 0; + let bodyScrollHeight = 0; + let documentScrollHeight = 0; + + if (document.body) { + bodyScrollHeight = document.body.scrollHeight; + } + + if (document.documentElement) { + documentScrollHeight = document.documentElement.scrollHeight; + } + + scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight; + + return scrollHeight; +} + +/** + * 滚动条在 y 轴上的滚动距离 + * + * @return {number} y 轴上的滚动距离 + */ +export function getScrollTop () { + let scrollTop = 0; + let bodyScrollTop = 0; + let documentScrollTop = 0; + + if (document.body) { + bodyScrollTop = document.body.scrollTop; + } + + if (document.documentElement) { + documentScrollTop = document.documentElement.scrollTop; + } + + scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop; + + return scrollTop; +} + +/** + * 浏览器视口的高度 + * + * @return {number} 浏览器视口的高度 + */ +export function getWindowHeight () { + const windowHeight = document.compatMode === 'CSS1Compat' + ? document.documentElement.clientHeight + : document.body.clientHeight; + + return windowHeight; +} + +/** + * 简单的 loadScript + * + * @param {string} url js 地址 + * @param {Function} callback 回调函数 + */ +export function loadScript (url, callback) { + const script = document.createElement('script'); + script.async = true; + script.src = url; + + script.onerror = () => { + callback(new Error(`Failed to load: ${url}`)); + }; + + script.onload = () => { + callback(); + }; + + document.getElementsByTagName('head')[0].appendChild(script); +} + +/** + * @description 不用正则匹配,如:var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)"); + * 因如一次性读取多个值,不如一次操作完,存在后再取。 + * @param strCookie {string} + * @return {{}} + */ +export function getCookies(strCookie = document.cookie) { + if (!strCookie) { + return {}; + } + const arrCookie = strCookie.split('; ');// 分割 + const cookiesObj = {}; + arrCookie.forEach((cookieStr) => { + const arr = cookieStr.split('='); + const [key, value] = arr; + if (key) { + cookiesObj[key] = value; + } + }); + return cookiesObj; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/auth/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/auth/index.vue" new file mode 100644 index 0000000..2e0e8fe --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/auth/index.vue" @@ -0,0 +1,78 @@ + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/exception/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/exception/index.vue" new file mode 100644 index 0000000..cd5e6f6 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/exception/index.vue" @@ -0,0 +1,85 @@ + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.css" new file mode 100644 index 0000000..728e3d0 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.css" @@ -0,0 +1,42 @@ +.list-warp { + display: inline-block; + min-width: 150px; + width: 100%; + } + .list-title { + background: wheat; + padding-left: 10px; + margin-bottom: 5px; + display: block; + } + .list-scroll { + overflow: hidden; + display: inline-block; + padding: 0px 10px; + width: 100%; + } + .hover { + overflow-y: auto; + } + .list-scroll .hide { + display: none; + } + + .item { + display: block; + width: 100%; + } + + .item li{ + line-height: 28px; + cursor: pointer; + font-size: 16px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + } + + .item li:hover { + color: #3a84ff; + } \ No newline at end of file diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.vue" new file mode 100644 index 0000000..e0a468d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/components/scroll/index.vue" @@ -0,0 +1,129 @@ + + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/app.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/app.css" new file mode 100644 index 0000000..52d382a --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/app.css" @@ -0,0 +1,46 @@ +@import './bk-patch.css'; + +@define-mixin is-left-mixin $needBgColor: true { + color: #63656E; + &:hover { + color: #3A84FF; + @if $needBgColor { + background: #F0F1F5; + } + } +}; + +@define-mixin popover-panel-mxin $width: 150px, $itemHoverColor: #3A84FF { + width: $width; + display: flex; + flex-direction: column; + background: #FFFFFF; + border: 1px solid #E2E2E2; + box-shadow: 0px 3px 4px 0px rgba(64,112,203,0.06); + padding: 6px 0; + margin: 0; + color: #63656E; + .nav-item { + flex: 0 0 32px; + display: flex; + align-items: center; + padding: 0 20px; + list-style: none; + &:hover { + color: $itemHoverColor; + cursor: pointer; + background-color: #F0F1F5; + } + } +} + +.full-width { + width: 100%; +} + +.center-content { + background-color: #fff; + display: flex; + justify-content: center; + align-items: center; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/bk-patch.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/bk-patch.css" new file mode 100644 index 0000000..c084cf5 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/bk-patch.css" @@ -0,0 +1,15 @@ +.navigation-bar { + width: 100%; + height: 100%; +} +.navigation-bar-container { + width: 100%; + max-width: 100%; +} + +.bk-navigation { + min-width: 1360px; +} +.bk-navigation-wrapper .navigation-container { + max-width: 100% !important; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/clearfix.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/clearfix.css" new file mode 100644 index 0000000..68b275f --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/clearfix.css" @@ -0,0 +1,10 @@ +@define-mixin clearfix { + &::after { + display: block; + clear: both; + content: ""; + font-size: 0; + height: 0; + visibility: hidden; + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/create-label.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/create-label.css" new file mode 100644 index 0000000..01b9b5e --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/create-label.css" @@ -0,0 +1,18 @@ +@define-mixin create-label $mainColor, $bgColor, $color, $fillColor { + & { + background-color: $bgColor; + border-color: $mainColor; + color: $color; + + &.is-fill { + background-color: $mainColor; + color: $fillColor; + } + + &.is-text { + border-color: #fff; + background-color: #fff; + color: $color; + } + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/ellipsis.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/ellipsis.css" new file mode 100644 index 0000000..bb9e1d6 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/ellipsis.css" @@ -0,0 +1,7 @@ +@define-mixin ellipsis $maxWidth, $display: inline-block { + display: $display; + max-width: $maxWidth; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/scroller.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/scroller.css" new file mode 100644 index 0000000..88a235c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/mixins/scroller.css" @@ -0,0 +1,11 @@ +@define-mixin scroller $backgroundColor: #e6e9ea, $width: 4px { + &::-webkit-scrollbar { + width: $width; + background-color: lighten($backgroundColor, 80%); + } + &::-webkit-scrollbar-thumb { + height: 5px; + border-radius: 2px; + background-color: $backgroundColor; + } +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/reset.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/reset.css" new file mode 100644 index 0000000..1776c6d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/reset.css" @@ -0,0 +1,43 @@ +/* // 浏览器重置样式 */ + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +ul, +li { + margin: 0; + padding: 0; + list-style: none; +} + +dl, +dt, +dd, +p { + margin: 0; + padding: 0; +} + +a { + text-decoration: none; +} + +button { + outline: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/variable.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/variable.css" new file mode 100644 index 0000000..28b4378 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/css/variable.css" @@ -0,0 +1,37 @@ +/* 存放常用变量 */ + +/* 边框色 */ +/* 相对较浅,常用于容器边框色 */ +$borderColor: #ebf0f5; +/* 相对较深,常用于输入框边框色 */ +$borderWeightColor: #dde4eb; + +/* 字体色 */ +$fontColor: #7b7d8a; +$fontWeightColor: #737987; + +/* 主体 icon 颜色 */ +$iconPrimaryColor: #3c96ff; +$primaryColor: #3c96ff; + +/* 成功 icon 颜色 */ +$iconSuccessColor: #34d97b; +$successColor: #34d97b; + +/* 失败 icon 颜色 */ +$iconFailColor: #ff5656; +$failColor: #ff5656; +$dangerColor: #ff5656; + +/* warning icon 颜色 */ +$iconWarningColor: #ffb400; +$warningColor: #ffb400; + +/* 淡色 */ +$primaryLightColor: #ebf4ff; + +/* 适配宽度 */ +$mediaWidth: 1500px; + +/* 背景 hover */ +$bgHoverColor: #fafbfd; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/403.png" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/403.png" new file mode 100644 index 0000000000000000000000000000000000000000..858e46a2d986c8addea1c56a70fa6b0d59cad8b8 GIT binary patch literal 16622 zcmaKUhgV$Jxi5&00MR8tAQ*aw8M=fd%FsbW?}&k!Vd#hsk|0qWIlgX*W5398T&`PO zW5-FH$WH9Fu5DS8tysvCWi{(vKfkQ|oV?}r)_Q-y{r%3jb-=#Y@%nvbfBT$$_Ws%d zX5??){fnik+TS9r9ax5hbDwK$MbJu+9v}1nBI%{_+ zvrP}#V{0d^BlZ@%)wXg37uol@xVR;awsp;SH8zwCPfuo9c~#c(;AyiqM@dU%m3kINr3UBx{7gx9_R`W) z){-e}dUhl`x45`CJI9!9G-l$8%(<1RdFyiK)Li_X6)Np>!?U9^^P|&Kv0TwQG`%oi zYQRqA2$M7VvQu-)O>kKDvUMgqH!Funx^rP;<3GQ2a#Fu~Zob97x!?bG;kmYz8GCk% zeQtVTb{L^^#Pd-zB^9%F>-_X=+w}Cr9WPp()AQ4F&gq%hiVDqZu_>LSQ?}`)xm4|* z#>SG`skwRU)Uds_vebZ8vPMU3C6)PQrb<&;RY5^zUT&_ru&Ascr>L@^EH~e5HkykH z@7!BCJ-je!pPIjOuT8(VJm-7wWjajG;K`Nt+0jM2t!j39GFCfw$>{g*rSf~%Q}^1w zcQ0k;@4Yu0o5^Nyw*>o+2^mCHPhAZ3v3*vv@d*T>`HN=D;Q1{m9eHfGn?cZt&4P!S+ z5>w9R)#{iHS?TG$nCwj58=4B48VVND-5Lks1>z0Vr?~VX5Rmo$o7JKPLzIhZgJ=og zUJ3EpYlm;YE!OQK!1sd2H=dR;z46(rqVM5J+S}Tex#n)`r@Zs_PkN^y?4W_t#5@Qc z|MAR&i=i!Jo!#Bt+-l{b%(Utk=*or1j&}C3OD=u=;;(h@fM5NbYYc<9YGgjxQ4r7wS{*)fA4HQ_ zz;FC<8hiqj02%-RfgmP=#n#}6=Fx%mGDlzsBnkNJ)n2vi8b(W?+GLpl_m9%mj3#c1 z{-JZfgD@_p{KeeRp;E%x35(Z`g36Azwl*HA0u->~ADLGpmpbY51EO;eKpFRw!qo2p zU4IEIHPkxnYwKufflepnfW|+49{h-n{){t$9jr3MKnU8i=Tg(N*!btKTOrm}{Gc+{g*(7zLC!Hw1b_q<)^2soii|QKf?2q3%-aGU2-wM! zpF1G~;mRjftbx5Cc7qwv<-5Twj2^%>xM-<1{y3OwUl)D{ogIp<%DnhC zcoK+C3<5D|BOnK8+~^j)cQNm8uRh3C<$3Eh>WmpaAH9$XGmpryfAKRgpt}i7UBg8H zt_OU?O~!+G`EihrV+By@uSgJg;t$fXJ}{n)0dqWDj7{9nnA6t`k&>3S$}AGFA{Z@T zzX))ruFXR;oBF`1m4bHu{|8<;32CwUF=zn8!$UCxnTbHJh`nq?Gu&HT`>2?NyIU-q zm6Ht{`=Eo#a~u=_0?xlFGP+je#_L?kP%I;GAJI}TJp-l5I-U?5(c80mH2*}IUv}A+ zTi61f4vVP}V^S{UVi3(GV)^p1A7(6`|K0pSM$~M#(|!D*5jX_;^j^_*-dhN@zs64t zsiCRm1G}NySSH!NiFxK=f8Oq>SYbi~?V!AdNeY06y7?5?+1D!5Dk0aC7r@6repO%} zKXgUQ3VOE{RBit2s}KS{C{g8%SX5EyS6G^DJOHpGlc5tF8iX7YU7d|ip-YWBcY>SL zO;CGTH(nuN00TK8Dmb$auz(C3&!s^O_Jc)(wjvM%by{c(VMQ_-1#$Te`$sR8LGcU& zO8^~W`h{l?fD5=<8)%>uO!Yy|X7x7|M@Bj_4nqf+YGYwLz}?EjGsOmO=k1;#Cc$

CL@_*&I~90KXv{0OzijXQGWXRxrZ?X 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 x?&g_!gKddH!DqYRyfRu#^vi{U<{Yt?2*& literal 0 HcmV?d00001 diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/404.png" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/404.png" new file mode 100644 index 0000000000000000000000000000000000000000..87f162c0beda17e4504c4390e770ec2ed5bcb96b GIT binary patch literal 17086 zcmaKUhgY1}nKxa4=uLEnj(~w-C<8<99npmlh&Drqp$q~citM;0jx1$;;<~Zdwz6J3 zPS#G`*4ees`6NrOk|SA_*xK_QpU>G%_Vd5+{eJUYy$~n4&wJ;+_de}?HGseS z;6K{i>V3Tb2TI-Q)H^PqqC!GCyqh&&7(T7M5jc?NTyQK$Y(z-mjDadT%)omvZ{(u7I7eZicay}G)ZwVIn{Tbjtu zDJm+;HW;&wMm=Wemru;wN7nT7%Zc~yP-9&lTbf$1Pub>myyM8IZN*-ifSt+_<`%Tu z&MzxB!C~2JBMaF%Sq2{I-o)0{|NqpvIc@f`z0Lu%l-L}+iv(4V~V*8}cZd;zTE$FJN9bVI=9iE!E*jAShIzH3dYHplgwvWt@ zSsQCg6L6QTsVR#&*I+6vG}h)76x0}Va%xRg`PDVG@V&9Nwz?|ERC({SHMX&pIqSUr z-e)blpUwZl&oUk67O--Sb!qCj)l#=)o6|XtZJzq!y%>M+e(JN9AKpvu4}LZqo5^Ny z_XPW%2^mCHOI-}Kep1Q{s`vn#%4q84h}FbVIxTK`wZY*aCEJo?a&{ z`(?C$MBiR6Dz8^`&4FKkPb|GhMS|CY+(G?fX{|iscKaRZHs^R}h)7UHmCh>(as#vt z0>VXruTH2{#Bx%*pcn1#zlrSa>`Vyv@xUPFvuE-%Aj3e7^YIaKU3h5#Brj(BieUlb zqn}>?G1s34O(b1=3L@MGOhbhJ;^P>kfD(#o#GDDy94flE-VEi!*aTxbMrUg8mid0frCm0Dw#uh=Pj_i zZ_b1FZ-J2nOt-7QY#zP&n?cRy>bMuv2#9Bx0WT4Zjet1cRAp#0K71-?j9Db8hcFZyz;nWVxQ#oH$_3_;H;A#-FkJvOynu-S z(A#?vFtjPdv@~YJ!pV+~4#?_#U}GQ_!;DpuIdr+y=&}Bg68du z=NMi`Lu#Jf4usCz0$f-ymcj=3-Pw0E3lz7Fna#apm)*Q6hMfTeqFfep@_Gj7Um)ww z`$I-`c+~-u^SHTKP)k>kDUl2DvBaRPUwpnHLFHb{I1GZ?&!5R})<#Z!GYvB6)NJh) zX$@OjWo0lls18~07rdKQY=@b_U_9-VYB5%Zj^WmgT|Vn)O)1E&V9gxJ2HT4|Z7_!3 z$x~EmmfDJ}CKe5M4@`mTQbY$oK`#vSfh|x80!vh%Wo-b%EQ$PyKGrdi1zM{sbe*)? zYoKyvN@k7aSenu2vnTHSI}6z-PF;bh8j9f#<_zIJe6Uo5D%cAzJTwZ<%)I_KPbn}| zLejOn&iUj83@`tp_`|1jCoE1`DZwE!Rrqpgo(DA50+Q;PB%i(k*3PZdeCs)WfJX&l z{4Tu?k;=T~*1IDn&p!Y;Yyfk%1kzEXW!!^&l1w8dern$PA%t= z&|#TebneXuq8LJe%~6o8V<1aG1-BjscPu5BzQ=6`xaY+SvLPo63$CtknHl3gdgCWD zJKU#Er>pa2lF-$HB^_k-egocNG#kBmtg}<)8wTlZ0k=PZY#dKbO|@zBe0VS}47h)C zA06TDC&OfVjUMd+4Ns6S&m@BbCmCa4qGq@A6IT{++r-tzu(v(~` z-Pp_a*3jG>4Dq)(`KMn$Cf1Rf=_7oygmeB>uW@*NdFj6&KcjtwX9+$CW{y@HpTF`; z3>QJ{9j0)4h1-hN&$K|WUjms&z-lZL;B}yk26918BBVN@PSvwI0WPp7R@V%dr@G8A z!))`aH8RmIoyi6VoJDj`jzZb(qGHwf5SxOJ$I(fMgk1Yoa;bf8AWiUpDm4&6Qc7x&Axb%=xdP8oowgLr=W zYShF$n+An$Fhfc!zyx`LkkuQZY?eM^V)X(HgP_^Mb%E=@0*hu4!$5Si;M&WFnz!#f zxc>l#w|~bZ5>!Fld3>Wbyk(O2&N(UOA-V)`RkywNo{UY^>pTU!Fhd{cZ3J(a3mppe z@Gpwsb4W0s$y5`3c1q-05oFJr3_KxJ=)?fN^Ly?a@Y$wwJH0n$$m|F#`ScYCZnt?= z%)$>jo_QVg9K8}d6o~Zn$+&eN25bQg>fAmspdXYr36;K$1Oci;GN5Z9i)W*+TmWAHje>GPBkT=8HQhnK zPF#N;oH=~L&a-l&px4SCZu=w430Ub*2vnYD-UBQH%Qyp*7`-ux>d{-qG05!%yKoK+ zV8Xj~csI((@e~vl{K`XhMVB6n35II2WvTu0!-5;X7Wu^skeTWZMUH=SgzWQ=&7+5z zfb>!eom@RO$(WCve+V2n|1kTXAhNoh!3RcEJ+J)viJv;&wr{+>+ z7)6ei=H!SSJoc0K{9{Y{d}7QP6oRfi4(1OE{`i55ueXfrp!S0LKmJKv%XF+WRDtWh z-E5-&1wpPJJ^@fYe1rH&4cx+E9w?cLW_EnDFvfrhPH5Upho*{>BqBWn(%I+ewBsL^ z&>GpyLI4#}-|pF=_`bc^+ax z0t^Z*$`jDMKtL9#U`}-K%WrF{D7+z0M)zn)Q#om|TJu#ZC+7z2pyPr3r=j@`1A>VkBq@pQ{X;;du4I^oSzs{nHNnP zm9FD`+@?TZ1G$e~Vj?&w2*SfBoTp#o%hfyxm=OVP0;MD~K`p3ch;ESTVbF9%m(N0I zU@i=#%JBSY$P@!uKyy$lkFsFCbRWbth%r|6y0`#6si%;42h`KS!t`T!ZG3w>1OiI< z0>nd~aAE=j7N17GNjiq3_b+gPV8Dw9-00w;Xx{4qZ^#p@sU=mW6F@~hDHYOx9 zV;lr<`5{p!Z@n&ximMF3k6OR_4MhHo_s!p$+}+(fHR+6zy}FpL><@c|hy#gFwTfp^!61zhcrR)9P7 z6m{pVnZr}eG@pb-K9THVX7-nFACtk~@`aYYHikyPcnI_ZBE29?pFF79p{#@W5&B4_ zzI6?H+iq{cDBg1Axl$_~$G}YSng`=DOK)BN1=z*YQMGk-9?*iVU;?$52EAs1;#b%O z&UGt0!A^h#ey&C+EbS}YqOaNxvvv@)3Hs{Axt+IoRyC!N-rY*vCJA#{I(kBeys~C7Eux&K`)Nqk2sq!Cc;XVy*kyKeOTz zOMJiq))^29IuM-F5&-EO(ICcj7GQ|zO%UF5!<%DlCjyUpktap)LhH7<*9|hBS??KWM(_@mv6?}7gB8hYYRQ}c4@l!CnL`$Cnmz~AF~VdN&0enDA9DHW zK8$$uB7Q1~Nu4Xl9jW9t1OaFaWySx|=l5~uCY&&F48 za5I822p%hPu%Pd+*3f25Y8sTDZZK}>dvN3xi*Y*2#LM)O1;ib>vN8nqY{~iI&I!AH zM!Tjdj-Sq86eN~G2)h^06Xw7B_KD#z$nj=$-SMvowNE}71uBu0!V9lDK*E8!%PB^~4t*C{5=Pf?t9RF&DoMp1)f&_CA?I2e_Fy8#g zr+kM70!46RJ-bf9XizS&BL$R$)%1fgLpd;;!M=y)?uC3;b}qgkGeha{N|4I_0;u>= zDR`OJA@{#?16-*90mnhzLBZ<0Dz^I@c+7@rXHV<(R~_F<%*9tKM>VyVj#?dGd-q`u z+u5zx+42C_>AUYk1kPW5*pow0RAQz+uO|^&2MD0k$3O=!oKa6OWrXkvy!8gJ-TK{x zf3KQPar1F70TZEeK^uNg`ReyDFpPE(mj`bAnhx3oeep0mI?x4T3)R512DU5z^W8Tz zqox!aU1Bic(tFWF|At(CVuiN@jx962ciWy=V{tt+u;F)<4(}ezXx{rw?26gM?mv~VAG&nkmsL&^elvi2tHko7dpP?5aXT4 z*bCAI_$UD;zrL4~OqDtl`-4go^Ahyx1MCL%ss^F{^%K(ZB_piaY;Mt@(rU165YVwG zvexmf0`JDtGFies`23j#(aLE&{WQaw2rA(tXz9j_JrL{=+!`=0!osH_&8srbwZ||3 z>j6wbXIBq40N$>xdU?nN5-1lD#(4ugKfs%2f>1a|Jd~hfmf3Mk z1}cmgut4s<;bWgOZ!+QX-DQIC6cL~PEU})C6u`_F0_k|65M=w=wpuWdp4o*QK5l-pm76?#{`%if=j3Sp)59($*>NBo|#l0Jqg{HKL5K2 z`|z!u)3}mnUkiX30y)0=_pKT3TKMBrp2~qRUwfHZl{WhU_@)_7I?shhUJ1URPPGjZlzjP+f)T`o%aW(_MGOM`xKDZ$0P@$$ZQcYl%41#7uz{)zoLbkxFFaP8a zJq%1n4Myc|Fiq^BB){mwfLSPa9|+57H!^D02>kRnzn7iQ5Eub2h)FX3`Rvc3^FV_1 zg=HwJMxog%z~&h+<%(9Q0P~C5vI=TiQz$z zpxBba%w)jmHx;QEzlf@PPpq7lZFQCgOrQsM;ifWek2W#zxNofgq!o>I{(>)FjA-riz1^LEMizZ0Rw>&N*h&?%~7xS>JwK z-C{n*@b6y3fYIB^1QQf<9xw`GJW6?hVD>&i>}b_;ncDd%J3&e>x3b7JkI9q;5s99M zYWeN}q=S5nU49Vi4A9&o3l453r?IZxtH7+A*M0&aVAYH|9_Ucg^2Ep-3;`Vg?L2w@ z8L_%SFqeUqLME6!SKcti)_;Tzh=~X=C$GMD=Or0^`_w-}DV70v3+T>2fVF0f+RLH` zouP(t(T;vEaKQ0vE(X!d81S`*!lRSSZx7FLwadjQxjuXM{TR*|Xya2Vsr;iyVpe;r zlbFPjDth0Z;|AEKD!hzzL=Ol!uP*Ue3CT=a9;T| z81GX3$y@23R~}>s;iB#Iorac}9l9cEAK1=YObvj?TOG5353lbwN0tI>!($W7@x@#? zl-T;zeaq2INWeC%a$bgE5W4*Q9>@3AC&MCco#8S9W~`Zh3Sm42SzPQ;%VM1jxtk0S z?>P*Lo@N}oQV^o5{Qg!lMsX@}_rmDhHshfJWJ+G!#Iyq=VHi zr1y)JlPtx&FwhC+Uda?>?gL*w>+LDp_xR<1D`439Z~;{BQnd#|8)!FfGHIbOmec9e zhNdR5sbw%w8AUMyO8@S$vk_UR*vW>pp(H{2C>>5X3h8E20Y9C93 z&>c?lNP-87j*mr)6LhQjj|%V3it67r655h%N}4ZSw#iE@>GuK z%fYi@k#u2F)Sz~iiJA$D7R$km1}Jm~`%cI8r@=?pnZK4eFiKDnDDMau&#l44O(g{J z_&ntKGi&rv*?<1~ix9w!O0gPs&QIPJJ5yr8Mf5ocm-(iq<56jS%%OgLxgH|)s9Xl` z^xG7D)2CB~%yrIxG9mZIJFu$=jWtybId8~3)<1g!Q1AzQsmS_E1GzZfG@vlIfcUsJ zrZ|rj4+>14P#4(u1dOO>SXDLSQqNcmS|RA~utb+K%@`n>abNuew7hGtgVVQL)2nhN zvyms`?pO#>s&P7Hn?^@v!oe9xgKm7cw0R7=S6~TEi%W^?*T$hKP)k773w)b!+>v=+ z4022Q_!;&efZe*K1=I%5|WFigwJ1p_ejKHsPi3QP5qPLfbHJTd9soYiA6aY zgN((f;^UA8Z|1CEKYd?)^&e!EsM)=s@l?5nFSeJlD6a$ zxR;)htCaC7&u?XH`3Ar?li&tcs`kjv2ec=1KZ3i@tMFpj@YMt35Udt~S?yFEEVGMU zRA4v&nJPN{@57ZooXY(7C+3YVLb4 zL1(@Rs;)g#!#vId9iO600Y0Rvd_)JIQYo)b`|we}&!2OV3#K_p)jJaStVr)_1>gCn zO>ov~YAM)PzwlrUFr5XFljbdhWv&JzJ{kc!3pjA&9r~nPMtP$iP=>qdl&cR$az{*x z-sWU*4P{PHnYs)QZ<#X256rNHV?h89$5&GgjO=a>)QO!lSePaHlH0!&6L_tru_l04 zPEW~G{y6`djSa>m*XCW0KuOQaYBQ9$L>C_Bwgiq}jL}9=tRS-&!zbX>t^k#W#)RG7 zee=XAU02U;I&V+KW9qv0LG>uXm~-5HQoz5sKS{0+lrozfzp9kGdrO#Fk6(CHrhq$s z5YBusMT4<40j4WZwSNhmRguz~wjeg}k!7&7Z7`31$G5MoytXtID?cI_F&*Rme0jbv z-viU|#o18qA7D`fRcaxq7oQXpc$hk%N*W#~K1_M})^iv&MWwLAd=Fvk6)cMqzD;q@ zwfQWHj~)TD)w2Bn2SFWcqGwEbEEV=x7(yVs6wFJ1;h!K_r)r1Lh6gLbkLP+Zy?Iiv zQYI>ZpPA6_+4Nj`6mwqSHna&>OpeAJV_k58Il0geVdPeZLCmgI&`eduif&(_7gRx* z2=8piPmy!`N5{rsVT$j?g(p+0F+K{K2XTenvd%6#e`<(ns+z_=9tmg$`GOjk@HYoT z2gGQ;`qTEKBK@tKnB?GEPA6C;{^g;?ee^qL|(nm=DKfi&pOV zXLJMx-XMmpYy*r_gVuB6IBN?e;MoBtSf(!nU>X!lh=|-q$NQoTf*{xh>0ZG&>*&)b z&ETr1>}$aLWI3~%VQq_X{G|jt(Y2?+_U$)BSC>SZxhoeCpjW3#hwgaNL$+n9x|1p3 zy>ZI&#S08Fz`p2s|KovdVspF_H;!Yl#+>nI!}kZ(GsFMz5*TppS+GqIJu4XN-(*-- zZa_1R;fDvKw|~pVWLGB}fX+onHW^3Y(C%yR?%ZO~_yDY#8zh~7ilwd<=R??Ou%Hl`y;SY z3s^v4Obo>ToO&#_bh^bu$5-afRukhgZUn2QTS6Uw0Rja`*ic;n`dX|%eUsTWE7wkj zm%A@sWh3a~sx0x%B)LRi0|Q-bN4t3;R<0%p#^2{a`}XD6cit#19@lFZtlr9Hn=qNr zbN@V282uH`>e8@tx_k(Ok56e9C_IILt_5zMW8hu+ab~I)z0H18dV={y+84)axc)mZ zpzfGRwF?ZCuc7HI+c}M@_}BT);k7xD2@#jbh`WP$z*P{CJt)#+SR{B9Xy>oC>sfJ9 zyPCjk`~21X35bs$7W2?HJ;RL)HuBk@882l!$W&nrMtg6*%G~M$g^ai`PaBP@GCulQ zB8D2W-T(aR{oF0Ub~n#R06dY(xr?X8#Pk+0{wlW6d^tBzbv>D&@q-nb>N4b8#GP<~ z?iQwDP77~T4{F%nnufT2t`S@?0>Qil-1n;<4}BICUF|9tz%QMlvD^;cUbFKylOh=e z1mP>|d{Nm@HbLD%j^F!%wruq+ZiQ@ZBXkdO~g`V5;_XS&_GXN$IaMj_cO5i;9)sw-lW$}5Mg>C~ptQJd+W)$T(1mmXbW*zDd zixs?RWE6ZKs0Gvx0!+;6RMEk+n71x69E8}-!Y|I%9RBA0cjS`ehkMK!K`jJB1|+K1 z#r^=$X%UPg-F5`z_#G8UfU5?B7GxN|wyY}h!$)iHIC-URd21Y&_s@pJmE0dVfPFVr1;=ON!H%guAp zfD5P05fD>M8WvUM_(Bf^;M>j}e)jqnxC#}tdw+Gr8v)&ny-jp*<2KkvM81tXS@Q=| zB@GQ21{LdL!L!5w{?Q8+;4LA;D&`oJbNjPiU~4{LM{?DrObpn2`rU>SAY6mFiouan zPy{t98Kap-0P-DN2fBJ{DrQiO;9LS=lFX_2eEz1`B3a9O5%A&5@@I%lBVegMw~>dK z)g~3OaJb71zQh5HlhN^uoltD5%f*$e-q8;uLnrX^V^uM1odb8@WA3P@{y{cq+BNj@ znFa9pbMh2jfyx{P=zK;CU}lF~Scs0VLU(@T_}j7KPyR(NXAJKb<&X&BArM~lspHQS zO9!FR|AP8~+rNUAI^xf;0`!0fms@IVY~)j^*-aHmIMX?g8w2d_4cuOfjD$1#ta|1o zz#Q=gU3wr4+z(U{pC15W39e+*7F>J{G8?o#CWZ~i?|0OEcB(4UG@mIpaLZqweMJWK z%&egCk&#WvZy$cQO3X<-z}Ck3X95bL^cZHr^?7x*_x|`wW(hZV7UVMODKcb(26Tme z43i*coV8Qy!9KuKgnN4qLogcvW*^rb&i&?>FP1>z6d-xSJ>1NBMayC^vJ3^R`C^bS wsB{vn-RH(T^oQv%6NuRYFi?P}`ka%i(>M>t89?|!{!b(tYui{4Rb1%*0ggDl#sB~S literal 0 HcmV?d00001 diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/500.png" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/500.png" new file mode 100644 index 0000000000000000000000000000000000000000..17cda81b9e5183d5704d22bfd4b92c4913477eb6 GIT binary patch literal 19351 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 { + injectCSRFTokenToHeaders(); + if (user.isAuthenticated) { + global.bus = bus; + global.mainComponent = new Vue({ + el: '#app', + router, + store, + components: { App }, + template: '', + }); + } else { + auth.redirectToLogin(); + } +}, (err) => { + let message; + if (err.status === 403) { + message = 'Sorry,您的权限不足!'; + if (err.data && err.data.msg) { + message = err.data.msg; + } + } else { + message = '无法连接到后端服务,请稍候再试。'; + } + + const divStyle = '' + + 'text-align: center;' + + 'width: 400px;' + + 'margin: auto;' + + 'position: absolute;' + + 'top: 50%;' + + 'left: 50%;' + + 'transform: translate(-50%, -50%);'; + + const h2Style = 'font-size: 20px;color: #979797; margin: 32px 0;font-weight: normal'; + + const content = '' + + `

` + + `

${message}

` + + '
'; + + document.write(content); +}); diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/public-path.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/public-path.js" new file mode 100644 index 0000000..5d8d0d9 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/public-path.js" @@ -0,0 +1,7 @@ +/** + * @file webpack public path config + * @author + */ + +// eslint-disable-next-line +__webpack_public_path__ = `${window.PROJECT_CONFIG.BK_STATIC_URL}/`; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/router/index.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/router/index.js" new file mode 100644 index 0000000..051f3a3 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/router/index.js" @@ -0,0 +1,155 @@ +/** + * @file router 配置 + * @author + */ + +import Vue from 'vue'; +import VueRouter from 'vue-router'; + +import store from '@/store'; +import http from '@/api'; +import preload from '@/common/preload'; + +const MainEntry = () => import(/* webpackChunkName: 'entry' */'@/views'); +// import MainEntry from '@/views' +const Example1 = () => import(/* webpackChunkName: 'example1' */'@/views/example1'); +// import Example1 from '@/views/example1' +const Example2 = () => import(/* webpackChunkName: 'example2' */'@/views/example2'); +// import Example2 from '@/views/example2' +const NotFound = () => import(/* webpackChunkName: 'none' */'@/views/404'); +// import NotFound from '@/views/404' + +const Demo = () => import('../views/demo'); +const List = () => import('../views/demo/list'); +const Detail = () => import('../views/demo/detail'); +const VuexDemo = () => import('../views/vuex'); +const Custom = () => import('../views/custom'); +Vue.use(VueRouter); + +const routes = [ + { + path: '/', + name: 'appMain', + component: MainEntry, + alias: '', + children: [ + { + path: '/demo', + name: 'demo', + component: Demo, + meta: { + matchRoute: 'demo', + }, + children: [ + { + path: 'list', + name: 'list', + component: List, + }, + { + path: 'detail/:id?', + name: 'detail', + component: Detail, + beforeEnter: (to, from, next) => { + next(); + }, + }, + ], + }, + { + path: 'vuex', + alias: '', + name: 'vuex', + component: VuexDemo, + meta: { + matchRoute: 'vuex', + }, + }, + { + path: 'example1', + alias: '', + name: 'example1', + component: Example1, + meta: { + matchRoute: '首页', + }, + }, + { + path: 'custom', + alias: '', + name: 'custom', + component: Custom, + meta: { + matchRoute: 'custom', + }, + }, + { + path: 'example2', + name: 'example2', + component: Example2, + meta: { + matchRoute: '登陆信息', + }, + }, + ], + }, + // 404 + { + path: '*', + name: '404', + component: NotFound, + }, +]; + +const router = new VueRouter({ + mode: 'history', + routes, +}); + +const cancelRequest = async () => { + const allRequest = http.queue.get(); + const requestQueue = allRequest.filter(request => request.cancelWhenRouteChange); + await http.cancel(requestQueue.map(request => request.requestId)); +}; + +let preloading = true; +let canceling = true; +let pageMethodExecuting = true; + +router.beforeEach(async (to, from, next) => { + canceling = true; + await cancelRequest(); + canceling = false; + next(); +}); + +router.afterEach(async (to) => { + store.commit('setMainContentLoading', true); + + preloading = true; + await preload(); + preloading = false; + + const pageDataMethods = []; + const routerList = to.matched; + routerList.forEach((r) => { + Object.values(r.instances).forEach((vm) => { + if (typeof vm.fetchPageData === 'function') { + pageDataMethods.push(vm.fetchPageData()); + } + if (typeof vm.$options.preload === 'function') { + pageDataMethods.push(vm.$options.preload.call(vm)); + } + }); + }); + + pageMethodExecuting = true; + await Promise.all(pageDataMethods); + pageMethodExecuting = false; + + if (!preloading && !canceling && !pageMethodExecuting) { + store.commit('setMainContentLoading', false); + } +}); + +export default router; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/index.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/index.js" new file mode 100644 index 0000000..0086540 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/index.js" @@ -0,0 +1,86 @@ +/* eslint-disable no-undef */ +/** + * @file main store + * @author + */ + +import Vue from 'vue'; +import Vuex from 'vuex'; + +import example from './modules/example'; +import vuexDemo from './modules/vuexDemo'; +import bbs from './modules/bbs'; +import http from '@/api'; +// import { getCookies } from '../common/util'; +Vue.use(Vuex); + +const store = new Vuex.Store({ + // 模块 + modules: { + example, + vuexDemo, + bbs, + }, + // 公共 store + state: { + mainContentLoading: false, + // 系统当前登录用户 + user: {}, + }, + // 公共 getters + getters: { + mainContentLoading: state => state.mainContentLoading, + user: state => state.user, + }, + // 公共 mutations + mutations: { + /** + * 设置内容区的 loading 是否显示 + * + * @param {Object} state store state + * @param {boolean} loading 是否显示 loading + */ + setMainContentLoading(state, loading) { + state.mainContentLoading = loading; + }, + + /** + * 更新当前用户 user + * + * @param {Object} state store state + * @param {Object} user user 对象 + */ + updateUser(state, user) { + state.user = Object.assign({}, user); + }, + }, + actions: { + /** + * 获取用户信息 + * + * @param {Object} context store 上下文对象 { commit, state, dispatch } + * + * @return {Promise} promise 对象 + */ + userInfo(context) { + // ajax 地址为 USER_INFO_URL,如果需要 mock,那么只需要在 url 后加上 AJAX_MOCK_PARAM 的参数, + // 参数值为 mock/ajax 下的路径和文件名,然后加上 invoke 参数,参数值为 AJAX_MOCK_PARAM 参数指向的文件里的方法名 + // 例如本例子里,ajax 地址为 USER_INFO_URL,mock 地址为 USER_INFO_URL?AJAX_MOCK_PARAM=index&invoke=getUserInfo + + // 后端提供的地址 + // const url = USER_INFO_URL + // mock 的地址,示例先使用 mock 地址, 修改mock/ajax/index文件 + // const loginUrl = window.PROJECT_CONFIG.BK_LOGIN_URL || window.BK_LOGIN_URL; + // const cookies = getCookies(); + // const url = `${loginUrl}api/v3/is_login/?bk_token=${cookies.bk_token}`; + const url = USER_INFO_URL; + return http.get(url).then((response) => { + const userData = response.data || {}; + context.commit('updateUser', userData); + return userData; + }); + }, + }, +}); + +export default store; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/bbs.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/bbs.js" new file mode 100644 index 0000000..612fc8c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/bbs.js" @@ -0,0 +1,26 @@ +import http from '@/api'; +export default { + namespaced: true, + state: { + bbsList: [], + }, + mutations: { + setBbsList(state, data) { + console.log(data); + state.bbsList = data.list; + }, + }, + actions: { + getBbsList(context) { + http.get('/bbs/data-source/user/tableName/bbs').then((res) => { + context.commit('setBbsList', res.data); + }); + }, + getDetail(context, detailId) { + return http.get(`/bbs/data-source/user/tableName/bbs/id/${detailId}`); + }, + createBBs(context, data) { + return http.post('/bbs/data-source/user/tableName/bbs', data); + }, + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/example.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/example.js" new file mode 100644 index 0000000..daee31c --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/example.js" @@ -0,0 +1,21 @@ +/** + * @file app store + * @author + */ + +import http from '@/api'; +import queryString from 'query-string'; + +export default { + namespaced: true, + state: { + }, + mutations: { + }, + actions: { + getTableData(context, params, config = {}) { + // eslint-disable-next-line no-undef + return http.get(`/table?&${queryString.stringify(params)}`, params, config); + }, + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/vuexDemo.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/vuexDemo.js" new file mode 100644 index 0000000..0fc710e --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/store/modules/vuexDemo.js" @@ -0,0 +1,22 @@ + +export default { + namespaced: true, + state: { + count: 0, + }, + actions: { + increment(context) { + context.commit('increment'); + }, + }, + getters: { + autoIncrement(state) { + return state.count * 10; + }, + }, + mutations: { + increment(state) { + state.count = state.count + 1; + }, + }, +}; diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/404.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/404.vue" new file mode 100644 index 0000000..8ff83e2 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/404.vue" @@ -0,0 +1,31 @@ + + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/custom/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/custom/index.vue" new file mode 100644 index 0000000..43be83d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/custom/index.vue" @@ -0,0 +1,35 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/detail.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/detail.vue" new file mode 100644 index 0000000..ccab0a8 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/detail.vue" @@ -0,0 +1,70 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/index.vue" new file mode 100644 index 0000000..edb1b77 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/index.vue" @@ -0,0 +1,35 @@ + + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/list.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/list.vue" new file mode 100644 index 0000000..a5fb9b8 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/demo/list.vue" @@ -0,0 +1,45 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.css" new file mode 100644 index 0000000..e69de29 diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.vue" new file mode 100644 index 0000000..9855b6d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example1/index.vue" @@ -0,0 +1,98 @@ + + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.css" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.css" new file mode 100644 index 0000000..358b3e1 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.css" @@ -0,0 +1,11 @@ +.avatar_url { + margin-top: 30px; + display: flex; + align-items: center; +} + +.avatar_url img { + display: inline-block; + margin-left: 10px; + border-radius: 100%; +} diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.vue" new file mode 100644 index 0000000..b2efbce --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/example2/index.vue" @@ -0,0 +1,28 @@ + + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/index.vue" new file mode 100644 index 0000000..69ade59 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/index.vue" @@ -0,0 +1,3 @@ + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/index.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/index.vue" new file mode 100644 index 0000000..0787d0d --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/index.vue" @@ -0,0 +1,36 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/state.vue" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/state.vue" new file mode 100644 index 0000000..86811c7 --- /dev/null +++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/views/vuex/state.vue" @@ -0,0 +1,29 @@ + + + + diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/images/favicon.png" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/images/favicon.png" new file mode 100644 index 0000000000000000000000000000000000000000..4d4767b5671e21c6b8fc16cc9206a18a37404d50 GIT binary patch literal 1472 zcmdT^Jq`gu82zwOh{#r=S|SQZpjSD9s8pg5AyH^t#8t#W9KscR^EPj?-|UP|!m{7n z_cxpIvmyvx(U=PN}KHb_z`^$iv%zO2k$*dF+rWI0xfjz!pq|2zSgz)K#UOz`MRx7~lUdpk~HX0$%v;I_ghqcOOJ^Trb2K`(cZE44Dl*=>C_( x6wo~!qQ~>*Hs&nm86%Hk@*8LS-%xj{Z+r^GmdF*?^;t>J(K(qWR~SZBxNnmTwW + + + + + 登陆成功 + + + + + diff --git a/README.md b/README.md index 50dc249..610d96a 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,9 @@ [将原有代码重新部署至社区版7.0](./9_将原有代码重新部署至社区版7.0/如何将原有代码重新部署至社区版7.0.md) + + +## 11. 前端进阶 + +[前端进阶](./11_前端进阶/README.md) + -- Gitee