Prechádzať zdrojové kódy

feat: 集成所有开发分支,解决冲突,整理Vuex,完成User与Project的Mock

Jinyao 5 rokov pred
rodič
commit
bb3c2ec612

+ 1 - 0
.eslintrc.js

@@ -18,5 +18,6 @@ module.exports = {
     'max-len': ['error', { code: 140, ignoreTrailingComments: true }],
     'no-param-reassign': [2, { props: false }],
     'linebreak-style': [0, 'error', 'windows'],
+    'import/no-cycle': 0,
   },
 };

+ 1 - 0
mock/index.js

@@ -14,6 +14,7 @@ app.use((req, res, next) => {
   res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
   res.header('Access-Control-Allow-Headers', 'X-Requested-With');
   res.header('Access-Control-Allow-Headers', 'Content-Type');
+  res.header('Access-Control-Allow-Headers', 'Authorization');
   next();
 });
 

+ 5 - 25
mock/routers/user.js

@@ -1,34 +1,14 @@
 /* eslint-disable import/no-extraneous-dependencies */
 /* eslint-disable @typescript-eslint/no-var-requires */
 const router = require('express').Router();
+const jwt = require('jsonwebtoken');
 const wrapper = require('../wrapper');
 
-router.get('/info/:id',
-  (req, res) => res.json(wrapper({
-    id: req.params.id,
-    username: 'kenny',
-  })));
-// query参数可以通过req.query.name取得
-router.get('/info',
-  (req, res) => {
-    const query = req.query || {};
-    return res.json(wrapper({
-      limit: query.limit,
-      offset: query.offset,
-      list: [{
-        username: 'admin1',
-        sex: 1,
-      }, {
-        username: 'admin2',
-        sex: 0,
-      }],
-    }));
-  });
+const SECRET = 'SEEC-1919810-OIDC-114514';
 
-router.post('/post',
+router.get('/self',
   (req, res) => {
-    console.log(req.body);
-    res.end();
+    const userinfo = jwt.decode(req.headers.authorization, SECRET);
+    res.json(wrapper(userinfo));
   });
-
 module.exports = router;

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
     "element-plus": "^1.0.2-beta.31",
+    "jsonwebtoken": "^8.5.1",
     "vue": "^3.0.0",
     "vue-axios": "^3.2.4",
     "vue-class-component": "^8.0.0-0",

+ 12 - 5
src/App.vue

@@ -7,17 +7,24 @@
 </template>
 
 <script lang="ts">
-import {
-  defineComponent,
-} from 'vue';
+import { defineComponent } from 'vue';
 import Menu from '@/components/Menu.vue';
 
-import { useProject } from '@/composable/useProject';
+import { useProjectList } from '@/composable/useProject';
+import { getVariableFromQueryString } from '@/utils/qs';
+import { WorkspaceDispatcher, UserDispatcher, TokenDispatcher } from './store/dispatchers';
 
 export default defineComponent({
   components: { Menu },
   setup() {
-    const { projectList, selectedProject, handleProjectChange } = useProject();
+    const token = getVariableFromQueryString('id_token');
+    if (token) {
+      TokenDispatcher.updateToken(token);
+    }
+
+    const { projectList, selectedProject, handleProjectChange } = useProjectList();
+    UserDispatcher.updateUserInfo();
+    WorkspaceDispatcher.refreshProjectList();
     return {
       projectList,
       selectedProject,

+ 2 - 2
src/components/Menu.vue

@@ -62,7 +62,7 @@ export default {
   display: flex;
   flex-direction: row;
   align-items: center;
-  justify-content: start;
+  justify-content: flex-start;
   z-index: 100;
 }
 .menu-logo{
@@ -84,7 +84,7 @@ export default {
   width: 240px;
   display: flex;
   flex-direction: row;
-  justify-content: start;
+  justify-content: flex-start;
   align-items: center;
   border-radius: 5px;
   position: relative;

+ 9 - 12
src/composable/useProject.ts

@@ -1,21 +1,18 @@
-import { computed, reactive } from 'vue';
+import { computed } from 'vue';
+import { useStore } from '@/store';
+import { WorkspaceDispatcher } from '@/store/dispatchers';
 import { useRouter } from 'vue-router';
-import { useStore } from 'vuex';
 
-export const useProject = () => {
+export const useProjectList = () => {
   const store = useStore();
   const router = useRouter();
 
-  const projectList = computed(() => store.state.projectList);
-  let selectedProject = reactive({});
+  const projectList = computed(() => store.state.workspace.projectList);
+  const selectedProject = computed(() => store.getters.currentProjectInfo);
 
-  const handleProjectChange = (id: number) => {
-    projectList.value.forEach((item: Project) => {
-      if (item.id === id) {
-        selectedProject = item;
-        router.push(`/project/${id}/schedule`);
-      }
-    });
+  const handleProjectChange = (projectId: number) => {
+    WorkspaceDispatcher.changeProject(projectId);
+    router.push(`/project/${projectId}/schedule`);
   };
   return {
     projectList,

+ 6 - 6
src/main.ts

@@ -1,14 +1,14 @@
 import { createApp } from 'vue';
-import axios from '@/utils/axios';
 import { Components, Plugins } from '@/element-plus';
 import 'element-plus/lib/theme-chalk/index.css';
-import App from './App.vue';
-import router from './router';
-import store from './store';
+import { store, key } from '@/store';
+import App from '@/App.vue';
+import router from '@/router';
 
 const app = createApp(App);
-app.use(router).use(store);
-app.config.globalProperties.axios = axios;
+
+app.use(router);
+app.use(store, key);
 
 Components.forEach((component) => {
   app.component(component.name, component);

+ 5 - 7
src/router/index.ts

@@ -1,4 +1,4 @@
-import { useStore } from 'vuex';
+import { isUserLoggedIn } from '@/utils/user';
 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
 import { HOME, LOGIN, PROJECT } from './name';
 
@@ -11,18 +11,16 @@ const AuthWrapper = (routeRecord: RouteRecordRaw) => {
   };
 };
 
-const store = useStore();
-
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/',
     redirect: '/home',
   },
-  AuthWrapper({
+  {
     path: '/home',
     name: HOME,
     component: () => import('@/views/Home.vue'),
-  }),
+  },
   {
     path: '/login',
     name: LOGIN,
@@ -73,7 +71,7 @@ router.beforeEach((to, from, next) => {
   if (to.matched.some((record) => record.meta.requiresAuth)) {
     // this route requires auth, check if logged in
     // if not, redirect to login page.
-    if (store.state.token === '') {
+    if (!isUserLoggedIn()) {
       next({
         name: LOGIN,
         query: { redirect: to.fullPath },
@@ -87,7 +85,7 @@ router.beforeEach((to, from, next) => {
 });
 
 router.beforeEach((to, from, next) => {
-  if (to.name === LOGIN && store.state.token === '') {
+  if (to.name !== LOGIN || !isUserLoggedIn()) {
     next();
   }
 });

+ 9 - 0
src/store/dispatchers/index.ts

@@ -0,0 +1,9 @@
+import * as UserDispatcher from './user';
+import * as WorkspaceDispatcher from './workspace';
+import * as TokenDispatcher from './token';
+
+export {
+  UserDispatcher,
+  WorkspaceDispatcher,
+  TokenDispatcher,
+};

+ 1 - 1
src/store/dispatchers.ts → src/store/dispatchers/token.ts

@@ -1,4 +1,4 @@
-import store from './index';
+import { store } from '../index';
 
 export const updateToken = (token: string) => {
   store.dispatch('updateToken', {

+ 5 - 0
src/store/dispatchers/user.ts

@@ -0,0 +1,5 @@
+import { store } from '../index';
+
+export const updateUserInfo = () => {
+  store.dispatch('updateUserInfo');
+};

+ 13 - 0
src/store/dispatchers/workspace.ts

@@ -0,0 +1,13 @@
+import { store } from '../index';
+
+export const refreshProjectList = () => {
+  store.dispatch('refreshProjectList', store.state.user.id);
+};
+
+export const changeProject = (projectId: number) => {
+  store.dispatch('changeProject', { projectId });
+};
+
+export const changeGroup = (groupId: number) => {
+  store.dispatch('changeGroup', { groupId });
+};

+ 13 - 42
src/store/index.ts

@@ -1,45 +1,16 @@
-import { destoryToken, saveToken } from '@/utils/token';
-import { createStore } from 'vuex';
-import { useRouter } from 'vue-router';
+import { InjectionKey } from 'vue';
+import { createStore, Store, useStore as baseUseStore } from 'vuex';
 
-import {
-  ChangeGroupPayload, ChangeProjectPayload, RootState, TokenPayload,
-} from './typings';
+import { RootState } from './typings';
 
-export default createStore<RootState>({
-  state: {
-    currentGroup: -1,
-    currentProject: -1,
-    token: localStorage.getItem('token') || '',
-  },
-  mutations: {
-    changeGroup(state, { groupId }: ChangeGroupPayload) {
-      state.currentGroup = groupId;
-    },
-    changeProject(state, { projectId }: ChangeProjectPayload) {
-      state.currentProject = projectId;
-    },
-    updateToken(state, { token }: TokenPayload) {
-      state.token = token;
-    },
-  },
-  actions: {
-    changeGroup({ commit }, payload: ChangeGroupPayload) {
-      commit('changeGroup', payload);
-    },
-    changeProject({ commit }, payload: ChangeProjectPayload) {
-      commit('changeProject', payload);
-    },
-    updateToken({ commit }, payload: TokenPayload) {
-      commit('updateToken', payload);
-      saveToken(payload.token);
-    },
-    destoryToken({ commit }) {
-      commit('updateToken', {
-        token: '',
-      } as TokenPayload);
-      destoryToken();
-      useRouter().push('/home');
-    },
-  },
+import modules from './modules';
+
+export const key: InjectionKey<Store<RootState>> = Symbol('store');
+
+export const store = createStore<RootState>({
+  modules,
 });
+
+export function useStore() {
+  return baseUseStore(key);
+}

+ 9 - 0
src/store/modules/index.ts

@@ -0,0 +1,9 @@
+import { user } from './user';
+import { workspace } from './workspace';
+import { token } from './token';
+
+export default {
+  user,
+  workspace,
+  token,
+};

+ 34 - 0
src/store/modules/token.ts

@@ -0,0 +1,34 @@
+import { saveToken, destoryToken } from '@/utils/token';
+import { useRouter } from 'vue-router';
+import { ActionTree, Module, MutationTree } from 'vuex';
+import { RootState, TokenPayload, TokenState } from '../typings';
+
+const DEFAULT: TokenState = {
+  token: localStorage.getItem('token') || '',
+};
+
+const actions: ActionTree<TokenState, RootState> = {
+  updateToken({ commit }, payload: TokenPayload) {
+    commit('updateToken', payload);
+    saveToken(payload.token);
+  },
+  destoryToken({ commit }) {
+    commit('updateToken', {
+      token: '',
+    } as TokenPayload);
+    destoryToken();
+    useRouter().push('/home');
+  },
+};
+
+const mutations: MutationTree<TokenState> = {
+  updateToken(state, { token }: TokenPayload) {
+    state.token = token;
+  },
+};
+
+export const token: Module<TokenState, RootState> = {
+  state: () => DEFAULT,
+  mutations,
+  actions,
+};

+ 22 - 8
src/store/modules/user.ts

@@ -1,24 +1,38 @@
+import { UserAPI } from '@/api';
 import { ActionTree, Module, MutationTree } from 'vuex';
 import { RootState, UserState } from '../typings';
 
-const GUEST: UserState = {
-  isUserLoggedIn: false,
+const DEFAULT: UserState = {
   username: 'GUEST',
-  groupList: [],
-  projectListOfGroup: [],
-  token: '',
+  email: '',
+  id: -1,
+  phone: '',
+  role: 'GUEST',
 };
 
 const actions: ActionTree<UserState, RootState> = {
-
+  async updateUserInfo({ commit }) {
+    const res = (await UserAPI.getUserInfo()) as unknown as {
+      user_info: any;
+    };
+    commit('updateUserInfo', res.user_info);
+  },
 };
 
 const mutations: MutationTree<UserState> = {
-
+  updateUserInfo(state, {
+    name, email, id, phone, role,
+  }) {
+    state.username = name;
+    state.email = email;
+    state.id = id;
+    state.phone = phone;
+    state.role = role;
+  },
 };
 
 export const user: Module<UserState, RootState> = {
-  state: () => GUEST,
+  state: () => DEFAULT,
   actions,
   mutations,
 };

+ 59 - 0
src/store/modules/workspace.ts

@@ -0,0 +1,59 @@
+import { ProjectAPI } from '@/api';
+import {
+  ActionTree, GetterTree, Module, MutationTree,
+} from 'vuex';
+import {
+  WorkspaceState, RootState, ChangeGroupPayload, ChangeProjectPayload,
+} from '../typings';
+
+const DEFAULT: WorkspaceState = {
+  currentProjectId: -1,
+  currentGroupId: -1,
+  groupList: [],
+  projectList: [],
+};
+
+const actions: ActionTree<WorkspaceState, RootState> = {
+  async refreshProjectList({ commit }, userId) {
+    const projectList = await ProjectAPI.getProjectListByUserId(userId);
+    commit('updateProjectList', projectList);
+  },
+  changeGroup({ commit }, payload: ChangeGroupPayload) {
+    commit('changeGroup', payload);
+  },
+  changeProject({ commit }, payload: ChangeProjectPayload) {
+    commit('changeProject', payload);
+  },
+};
+
+const mutations: MutationTree<WorkspaceState> = {
+  updateProjectList(state, payload: Array<Project>) {
+    state.projectList = payload;
+  },
+  changeGroup(state, { groupId }: ChangeGroupPayload) {
+    state.currentGroupId = groupId;
+  },
+  changeProject(state, { projectId }: ChangeProjectPayload) {
+    state.currentProjectId = projectId;
+  },
+};
+
+const getters: GetterTree<WorkspaceState, RootState> = {
+  projectListOfCurrentGroup(state) {
+    return state.projectList.filter((project) => project.groupId === state.currentGroupId);
+  },
+  currentProjectInfo(state) {
+    return Array.prototype.find.call(state.projectList, (project) => project.id === state.currentProjectId)
+      || {
+        id: -1,
+        name: '未选择项目',
+      };
+  },
+};
+
+export const workspace: Module<WorkspaceState, RootState> = {
+  state: () => DEFAULT,
+  actions,
+  mutations,
+  getters,
+};

+ 22 - 9
src/store/typings.ts

@@ -1,25 +1,38 @@
-type groupId = number;
-type projectId = number;
+type GroupId = number;
+type ProjectId = number;
 
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
 export interface RootState {
-  currentGroup: groupId;
-  currentProject: projectId;
+  token: TokenState;
+  user: UserState;
+  workspace: WorkspaceState;
+}
+
+export interface TokenState {
   token: string;
 }
 
 export interface UserState {
-  isUserLoggedIn: boolean;
+  email: string;
+  id: number;
+  phone: string;
+  role: Role;
   username: string;
-  groupList: Array<groupId>;
-  projectListOfGroup: Record<groupId, Array<projectId>>;
+}
+
+export interface WorkspaceState {
+  currentGroupId: GroupId;
+  currentProjectId: ProjectId;
+  groupList: Array<GroupId>;
+  projectList: Array<Project>;
 }
 
 export interface ChangeGroupPayload {
-  groupId: groupId;
+  groupId: GroupId;
 }
 
 export interface ChangeProjectPayload {
-  projectId: projectId;
+  projectId: ProjectId;
 }
 
 export interface TokenPayload {

+ 7 - 0
src/typings/project.d.ts

@@ -6,6 +6,11 @@ declare interface Project {
   gitRemoteUrl: string;
 }
 
+declare interface Group {
+  id: number;
+  name: string;
+}
+
 declare type TreeNodeType = '0:需求' | '1:任务';
 declare type TreeNodeState = '0:已创建' | '1:进行中' | '2:已结束';
 
@@ -26,3 +31,5 @@ declare interface TreeNode {
   type: TreeNodeType;
   userId: number;
 }
+
+declare type Role = 'TEACHER' | 'STUDENT' | 'ADMIN' | 'GUEST';

+ 0 - 0
src/shims-vue.d.ts → src/typings/shims-vue.d.ts


+ 12 - 0
src/typings/vuex.d.ts

@@ -0,0 +1,12 @@
+import { ComponentCustomProperties } from 'vue';
+import { Store } from 'vuex';
+
+declare module '@vue/runtime-core' {
+  // declare your own store states
+  import RootState from '@/store/typings';
+
+  // provide typings for `this.$store`
+  interface ComponentCustomProperties {
+    $store: Store<State>;
+  }
+}

+ 4 - 2
src/utils/axios.ts

@@ -1,6 +1,8 @@
 /* eslint-disable */
 import axios from 'axios';
-import { destroyToken } from '@/store/dispatchers';
+import { TokenDispatcher } from '@/store/dispatchers';
+
+const { destroyToken } = TokenDispatcher;
 
 // Full config:  https://github.com/axios/axios#request-config
 // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
@@ -43,7 +45,7 @@ _axios.interceptors.response.use(
       destroyToken();
       return;
     }
-    return response.data.message;
+    return response.data.data;
   },
   (error) =>
     // Do something with response error

+ 10 - 0
src/utils/qs.ts

@@ -0,0 +1,10 @@
+export const getVariableFromQueryString = (name: string) => {
+  const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
+
+  const res = window.location.search.substr(1).match(reg);
+
+  if (res != null) {
+    return unescape(res[2]);
+  }
+  return null;
+};

+ 4 - 0
src/utils/token.ts

@@ -1,7 +1,11 @@
 export const saveToken = (token: string) => {
   localStorage.setItem('token', token);
+  localStorage.setItem('user', JSON.stringify({
+    login: true,
+  }));
 };
 
 export const destoryToken = () => {
   localStorage.removeItem('token');
+  localStorage.removeItem('user');
 };

+ 4 - 0
src/utils/user.ts

@@ -0,0 +1,4 @@
+export const isUserLoggedIn = () => {
+  const user = JSON.parse(localStorage.getItem('user') || '{}');
+  return user.login || false;
+};

+ 7 - 11
src/views/Home.vue

@@ -2,7 +2,7 @@
   <div class="home">
     <div class="poster">
     </div>
-    <div style="font-weight: bolder;font-size: 32px;padding: 30px">Hello,username</div>
+    <div style="font-weight: bolder;font-size: 32px;padding: 30px">Hello,{{username}}</div>
     <div class="project-list">
       <ProjectCard
         v-for="project in projectList"
@@ -16,25 +16,21 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
+import { defineComponent, computed } from 'vue';
 import ProjectCard from '@/components/ProjectCard.vue';
-import { useStore } from 'vuex';
-import { useRoute } from 'vue-router';
-import { updateToken } from '@/store/dispatchers';
+import { useProjectList } from '@/composable/useProject';
+import { useStore } from '@/store';
 
 export default defineComponent({
   components: {
     ProjectCard,
   },
   setup() {
+    const { projectList } = useProjectList();
     const store = useStore();
-    const route = useRoute();
-    const token = (route.query.id_token || null) as (string | null);
-    if (token) {
-      updateToken(token);
-    }
     return {
-      projectList: computed(() => store.state.projectList),
+      projectList,
+      username: computed(() => store.state.user.username),
     };
   },
 });

+ 1 - 1
src/views/Project/Schedule.vue

@@ -7,7 +7,7 @@
 import Tree from '@/components/Tree.vue';
 import RightForm from '@/components/RightForm.vue';
 
-import RequirementAPI from '@/api';
+import { RequirementAPI } from '@/api';
 
 export default {
   name: 'Schedule',

+ 1 - 1
src/views/ProjectContainer.vue

@@ -35,7 +35,7 @@ export default {
   box-sizing: border-box;
   display: flex;
   flex-direction: column;
-  justify-content: start;
+  justify-content: flex-start;
   align-items: center;
 }
 </style>

+ 80 - 0
yarn.lock

@@ -2365,6 +2365,11 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4
     escalade "^3.1.1"
     node-releases "^1.1.70"
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/buffer-equal-constant-time/download/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
+
 buffer-from@^1.0.0:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -3634,6 +3639,13 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.npm.taobao.org/ecdsa-sig-formatter/download/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha1-rg8PothQRe8UqBfao86azQSJ5b8=
+  dependencies:
+    safe-buffer "^5.0.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -5719,6 +5731,22 @@ jsonfile@^6.0.1:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonwebtoken@^8.5.1:
+  version "8.5.1"
+  resolved "https://registry.npm.taobao.org/jsonwebtoken/download/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
+  integrity sha1-AOceC431TCEhofJhN98igGc7zA0=
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^5.6.0"
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -5729,6 +5757,23 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.npm.taobao.org/jwa/download/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha1-dDwymFy56YZVUw1TZBtmyGRbA5o=
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.npm.taobao.org/jws/download/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha1-ABCZ82OUaMlBQADpmZX6UvtHgwQ=
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
 killable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npm.taobao.org/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -5934,6 +5979,36 @@ lodash.defaultsdeep@^4.6.1:
   resolved "https://registry.npm.taobao.org/lodash.defaultsdeep/download/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
   integrity sha1-US6b1yHSctlOPTpjZT+hdRZ0HKY=
 
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.npm.taobao.org/lodash.includes/download/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npm.taobao.org/lodash.isboolean/download/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.npm.taobao.org/lodash.isinteger/download/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
+
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npm.taobao.org/lodash.isnumber/download/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.npm.taobao.org/lodash.isplainobject/download/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
+
+lodash.isstring@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.npm.taobao.org/lodash.isstring/download/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+  integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+
 lodash.kebabcase@^4.1.1:
   version "4.1.1"
   resolved "https://registry.npm.taobao.org/lodash.kebabcase/download/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -5949,6 +6024,11 @@ lodash.memoize@^4.1.2:
   resolved "https://registry.npm.taobao.org/lodash.memoize/download/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
   integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
 
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.npm.taobao.org/lodash.once/download/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
+
 lodash.transform@^4.6.0:
   version "4.6.0"
   resolved "https://registry.npm.taobao.org/lodash.transform/download/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0"