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 0000000000000000000000000000000000000000..1a0241303f2399a201090a2b961cb3bd28b5b813
--- /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 0000000000000000000000000000000000000000..331cb5cecccb58ed8eac8b5ae6df5b20a60f4ee3
--- /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 0000000000000000000000000000000000000000..524cb55242b53f6a64cc646ea05db6acc7696d2d
--- /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 0000000000000000000000000000000000000000..9daef735d6eebf04a43b8d1ab7042c1cbcbf095c
--- /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 0000000000000000000000000000000000000000..d53f63d70c3564d728c1cf41ce066cddb5065745
--- /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 0000000000000000000000000000000000000000..42f2b5c6f43f24a7431f23d697b7b50d66f540d3
--- /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 0000000000000000000000000000000000000000..1380da53bff0abeceb13f0c60b98ab225b2d9012
--- /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 0000000000000000000000000000000000000000..a0642d365059d0ad0f4784f16019a7059a80d966
--- /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 0000000000000000000000000000000000000000..2508a839a0593e16f7531e67b777437ef047c7b6
--- /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 0000000000000000000000000000000000000000..39a7666993d6016558a8ea90dc8d2203099b6bff
--- /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 0000000000000000000000000000000000000000..c4f6b74f9d303291602eada95d2d44e0ff7c7333
--- /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 0000000000000000000000000000000000000000..c70293bc36930d2f9426fcf530e26d28c96ac948
--- /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 0000000000000000000000000000000000000000..ba7cdc1f444e05b8564d8b72c4c8ce87a2a43b88
--- /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 0000000000000000000000000000000000000000..7ca8a3575e9229c28323e08d2f69cc7baf893bc2
--- /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 0000000000000000000000000000000000000000..51d7e485c1c7eeab76e5e84aadf9952fd8a0666d
--- /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 0000000000000000000000000000000000000000..f6088949cc1ed83fff9effd0a7cecfbf37b404d3
--- /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 0000000000000000000000000000000000000000..c084357d679de9d44ee49e69f4dc4378d0e7b4e0
--- /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 0000000000000000000000000000000000000000..d774c200ac1cde48985c9e3d554080e22879383f
--- /dev/null
+++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/App.vue"
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+ {{child.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..e5a07ad7d21c4c48f985147e541631e9c12d139e
--- /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 0000000000000000000000000000000000000000..4fa04ece46e77884035b6745c478ab76ff123139
--- /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 0000000000000000000000000000000000000000..64f54665aa47d8111c9d9f61c60b058d9dcf1a40
--- /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 0000000000000000000000000000000000000000..3c46e8c0d34e8f8805cb25e6d44d502b5db96e48
--- /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 0000000000000000000000000000000000000000..690e088d6b8c997f2c56501369b81ef1502b3ce9
--- /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 0000000000000000000000000000000000000000..8dd557bb609e804be5e73ed296c2319867142d30
--- /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 0000000000000000000000000000000000000000..e31306864b9bdf617ca7cdc8b76ec3c4b60f0262
--- /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 0000000000000000000000000000000000000000..1fa2e3e7b25c75952f8d9206b525e5f79f448987
--- /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 0000000000000000000000000000000000000000..2eba092d3ce5526352877404c602d2ec461925ed
--- /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 0000000000000000000000000000000000000000..c9fbbc529f9af6c65508c5a0ee47d1d2837cd364
--- /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 0000000000000000000000000000000000000000..2e0e8feb4bccba021c57592c9139c434ab8d67d0
--- /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 0000000000000000000000000000000000000000..cd5e6f6ecf5a729cc289a2d2ab07019c42fa2bd1
--- /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 @@
+
+
+
![]()
+
+
+
+
+ {{ message }}
+
+
+
+
+
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 0000000000000000000000000000000000000000..728e3d07370669443098b3774008e779c15bdfdc
--- /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 0000000000000000000000000000000000000000..e0a468d87e82c24aa953c3954ed323fc35d6c853
--- /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 0000000000000000000000000000000000000000..52d382a74488c0c38b3a19abf172beb2945b72cc
--- /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 0000000000000000000000000000000000000000..c084cf585bb77e4e992ff20d7d37811b8fd1ee24
--- /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 0000000000000000000000000000000000000000..68b275f2de221d0af094374dd14a0a06dd36e72c
--- /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 0000000000000000000000000000000000000000..01b9b5e321326f1be653f136f4b600fbde6651e1
--- /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 0000000000000000000000000000000000000000..bb9e1d62eca94b703ebea29d8dbf80cc87e9b4ba
--- /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 0000000000000000000000000000000000000000..88a235cbc086f1f9e8522444aca4869f7f30b42a
--- /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 0000000000000000000000000000000000000000..1776c6d4406a93e4513e8054bb5ff9fbf96a71d3
--- /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 0000000000000000000000000000000000000000..28b43787419d75c6e431f984888ac54798e2edec
--- /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
Binary files /dev/null and "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/403.png" differ
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
Binary files /dev/null and "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/404.png" differ
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
Binary files /dev/null and "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/500.png" differ
diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/building.png" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/building.png"
new file mode 100644
index 0000000000000000000000000000000000000000..a29a367c80092aebc0add1c1f8185141269c220f
Binary files /dev/null and "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/images/building.png" differ
diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/main.js" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/main.js"
new file mode 100644
index 0000000000000000000000000000000000000000..131d68215dabec355d6bd89190c795fecf8940a4
--- /dev/null
+++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/src/main.js"
@@ -0,0 +1,68 @@
+/**
+ * @file main entry
+ * @author
+ */
+import './public-path';
+import Vue from 'vue';
+
+import App from '@/App';
+import router from '@/router';
+import store from '@/store';
+import { injectCSRFTokenToHeaders } from '@/api';
+import auth from '@/common/auth';
+import Img403 from '@/images/403.png';
+import Exception from '@/components/exception';
+import { bus } from '@/common/bus';
+import AuthComponent from '@/components/auth';
+import '@/common/bkmagic';
+import mavonEditor from 'mavon-editor';
+import 'mavon-editor/dist/css/index.css';
+// use
+Vue.use(mavonEditor);
+
+Vue.component('AppException', Exception);
+Vue.component('AppAuth', AuthComponent);
+
+auth.requestCurrentUser().then((user) => {
+ 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 0000000000000000000000000000000000000000..5d8d0d9c1c6c43faf0d020c513a8b7f66a815e8b
--- /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 0000000000000000000000000000000000000000..051f3a3f93d5a2b2ead7cf7581f6650976efe5f8
--- /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 0000000000000000000000000000000000000000..0086540a4911a1006db8ba092374efbbe362a31a
--- /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 0000000000000000000000000000000000000000..612fc8c542ae60fe66dc47d71dac6184e0843c48
--- /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 0000000000000000000000000000000000000000..daee31c5f23486b436ae91419d954485cbed4eed
--- /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 0000000000000000000000000000000000000000..0fc710eef16ff90b002d1e9d8506c4d94ebe20fb
--- /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 0000000000000000000000000000000000000000..8ff83e29711758ad8e26199841b3f0125644496a
--- /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 0000000000000000000000000000000000000000..43be83d9c68820af5de05fc01c050a47ab411dbc
--- /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 0000000000000000000000000000000000000000..ccab0a8c62f92cd125edf520277bb46fe2456cec
--- /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 @@
+
+
+
+
+
+ {{detailData.title}}
+
+
+
+
+
+ {{ detailId ? '返回' : '保存'}}
+
+
+
+
+
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 0000000000000000000000000000000000000000..edb1b771352e312e70c26f5d38888089955855b6
--- /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 @@
+
+
+
+ list页面
+ detail页面
+
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..a5fb9b80706a77ddd2b491875536aaa10fc8e663
--- /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 @@
+
+
+
+
+
+
+
+ {{props.row.title}}
+
+
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
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 0000000000000000000000000000000000000000..9855b6daf1e9b42d0eb285cd0b7efb3aee0682bb
--- /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 0000000000000000000000000000000000000000..358b3e17ccc80c35dbccdd46e883a879708783ab
--- /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 0000000000000000000000000000000000000000..b2efbce905529f6a019a1f5266cce66cfa9488d3
--- /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 @@
+
+
+
+ 用户名: {{$store.getters.user.username}}
+ 头像:
![]()
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..69ade599292a4f0d9b661bc6b5caad374b3304c7
--- /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 0000000000000000000000000000000000000000..0787d0debf7ae57bad48e01bc96756642e45b08f
--- /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 @@
+
+
+ this is vue Demo
+
+ mapActions
+
+ store
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..86811c781fe6fae6bbde9ce01b7d1e8d949243b4
--- /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 @@
+
+
+
{{count}}
+
{{autoIncrement}}
+
+
+
+
+
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
Binary files /dev/null and "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/images/favicon.png" differ
diff --git "a/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/login_success.html" "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/login_success.html"
new file mode 100644
index 0000000000000000000000000000000000000000..d74c717a1033cf7df58a116f91cb01122acd4644
--- /dev/null
+++ "b/11_\345\211\215\347\253\257\350\277\233\351\230\266/static/login_success.html"
@@ -0,0 +1,14 @@
+
+
+
+
+
+ 登陆成功
+
+
+
+
+
diff --git a/README.md b/README.md
index 50dc2495f561639a057ad9b2963743287c111407..610d96ad9ef872204ff2de9161adcd7282b66fa8 100644
--- a/README.md
+++ b/README.md
@@ -58,3 +58,9 @@
[将原有代码重新部署至社区版7.0](./9_将原有代码重新部署至社区版7.0/如何将原有代码重新部署至社区版7.0.md)
+
+
+## 11. 前端进阶
+
+[前端进阶](./11_前端进阶/README.md)
+