lex 1 éve
szülő
commit
138e49294c

+ 1 - 1
.husky/pre-commit

@@ -2,4 +2,4 @@
 . "$(dirname -- "$0")/_/husky.sh"
 
 # eslint --fix ./src --ext .vue,.js,.ts
-npx lint-staged
+# npx lint-staged

+ 1 - 14
index.html

@@ -18,25 +18,12 @@
   <!-- 避免IE使用兼容模式 -->
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="HandheldFriendly" content="true" />
-  <!-- uc强制竖屏 -->
-  <meta name="screen-orientation" content="portrait" />
-  <!-- QQ强制竖屏 -->
-  <meta name="x5-orientation" content="portrait" />
-  <!-- UC强制全屏 -->
-  <meta name="full-screen" content="yes" />
-  <!-- QQ强制全屏 -->
-  <meta name="x5-fullscreen" content="true" />
-  <!-- UC应用模式 -->
-  <meta name="browsermode" content="application" />
-  <!-- QQ应用模式 -->
-  <meta name="x5-page-mode" content="app" />
   <!-- 设置在apple上以应用模式启动时,是否全屏 -->
   <meta name="apple-touch-fullscreen" content="yes" />
   <!-- windows phone 点击无高光 -->
   <meta name="msapplication-tap-highlight" content="no" />
   <meta name="referrer" content="no-referrer" />
-  <title>学校端</title>
-  <script src="/flexible.js" charset="UTF-8"></script>
+  <title>老师端</title>
 </head>
 
 <body>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 8535 - 1
package-lock.json


+ 3 - 0
package.json

@@ -26,6 +26,7 @@
     "clean-deep": "^3.4.0",
     "dayjs": "^1.11.7",
     "numeral": "^2.0.6",
+    "pinia": "^2.1.4",
     "umi-request": "^1.4.0",
     "vue": "^3.2.47",
     "vue-router": "^4.1.6",
@@ -34,6 +35,7 @@
   "devDependencies": {
     "@babel/core": "^7.21.4",
     "@babel/preset-env": "^7.21.4",
+    "@types/crypto-js": "^4.1.1",
     "@types/node": "^16.18.23",
     "@typescript-eslint/eslint-plugin": "^5.57.1",
     "@typescript-eslint/parser": "^5.57.1",
@@ -45,6 +47,7 @@
     "@vue/eslint-config-typescript": "^11.0.2",
     "autoprefixer": "^10.4.14",
     "babel-plugin-dynamic-import-node": "^2.3.3",
+    "crypto-js": "^4.1.1",
     "eslint": "^8.38.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^9.10.0",

BIN
public/favicon.ico


+ 0 - 99
public/flexible.js

@@ -1,99 +0,0 @@
-!(function (a, b) {
-  function c() {
-    var b = f.getBoundingClientRect().width;
-    b / i > 420 && (b = 420 * i);
-    var c = b / 10;
-    (f.style.fontSize = c + 'px'), (k.rem = a.rem = c);
-  }
-  var d,
-    e = a.document,
-    f = e.documentElement,
-    g = e.querySelector('meta[name="viewport"]'),
-    h = e.querySelector('meta[name="flexible"]'),
-    i = 0,
-    j = 0,
-    k = b.flexible || (b.flexible = {});
-  if (g) {
-    console.warn('将根据已有的meta标签来设置缩放比例');
-    var l = g.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
-    l && ((j = parseFloat(l[1])), (i = parseInt(1 / j)));
-  } else if (h) {
-    var m = h.getAttribute('content');
-    if (m) {
-      var n = m.match(/initial\-dpr=([\d\.]+)/),
-        o = m.match(/maximum\-dpr=([\d\.]+)/);
-      n && ((i = parseFloat(n[1])), (j = parseFloat((1 / i).toFixed(2)))),
-        o && ((i = parseFloat(o[1])), (j = parseFloat((1 / i).toFixed(2))));
-    }
-  }
-  if (!i && !j) {
-    var p = a.navigator.userAgent,
-      q = (!!p.match(/android/gi), !!p.match(/iphone/gi)),
-      r = q && !!p.match(/OS 9_3/),
-      s = a.devicePixelRatio;
-    (i =
-      q && !r
-        ? s >= 3 && (!i || i >= 3)
-          ? 3
-          : s >= 2 && (!i || i >= 2)
-          ? 2
-          : 1
-        : 1),
-      (j = 1 / i);
-  }
-  if ((f.setAttribute('data-dpr', i), !g))
-    if (
-      ((g = e.createElement('meta')),
-      g.setAttribute('name', 'viewport'),
-      g.setAttribute(
-        'content',
-        'initial-scale=' +
-          j +
-          ', maximum-scale=' +
-          j +
-          ', minimum-scale=' +
-          j +
-          ', user-scalable=no'
-      ),
-      f.firstElementChild)
-    )
-      f.firstElementChild.appendChild(g);
-    else {
-      var t = e.createElement('div');
-      t.appendChild(g), e.write(t.innerHTML);
-    }
-  a.addEventListener(
-    'resize',
-    function () {
-      clearTimeout(d), (d = setTimeout(c, 300));
-    },
-    !1
-  ),
-    a.addEventListener(
-      'pageshow',
-      function (a) {
-        a.persisted && (clearTimeout(d), (d = setTimeout(c, 300)));
-      },
-      !1
-    ),
-    'complete' === e.readyState
-      ? (e.body.style.fontSize = 12 * i + 'px')
-      : e.addEventListener(
-          'DOMContentLoaded',
-          function () {
-            e.body.style.fontSize = 12 * i + 'px';
-          },
-          !1
-        ),
-    c(),
-    (k.dpr = a.dpr = i),
-    (k.refreshRem = c),
-    (k.rem2px = function (a) {
-      var b = parseFloat(a) * this.rem;
-      return 'string' == typeof a && a.match(/rem$/) && (b += 'px'), b;
-    }),
-    (k.px2rem = function (a) {
-      var b = parseFloat(a) / this.rem;
-      return 'string' == typeof a && a.match(/px$/) && (b += 'rem'), b;
-    });
-})(window, window.lib || (window.lib = {}));

+ 34 - 0
src/enums/httpEnum.ts

@@ -0,0 +1,34 @@
+/**
+ * @description: 请求结果集
+ */
+export enum ResultEnum {
+  SUCCESS = 200,
+  ERROR = -1,
+  TIMEOUT = 10042,
+  TYPE = 'success'
+}
+
+/**
+ * @description: 请求方法
+ */
+export enum RequestEnum {
+  GET = 'GET',
+  POST = 'POST',
+  PATCH = 'PATCH',
+  PUT = 'PUT',
+  DELETE = 'DELETE'
+}
+
+/**
+ * @description:  常用的contentTyp类型
+ */
+export enum ContentTypeEnum {
+  // json
+  JSON = 'application/json;charset=UTF-8',
+  // json
+  TEXT = 'text/plain;charset=UTF-8',
+  // form-data 一般配合qs
+  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+  // form-data  上传
+  FORM_DATA = 'multipart/form-data;charset=UTF-8'
+}

+ 14 - 0
src/enums/pageEnum.ts

@@ -0,0 +1,14 @@
+export enum PageEnum {
+  // 登录
+  BASE_LOGIN = '/login',
+  BASE_LOGIN_NAME = 'Login',
+  //重定向
+  // REDIRECT = '/redirect',
+  // REDIRECT_NAME = 'Redirect',
+  // 首页
+  BASE_HOME = '/home'
+  // //首页跳转默认路由
+  // BASE_HOME_REDIRECT = '/dashboard/console',
+  // // 错误
+  // ERROR_PAGE_NAME = 'ErrorPage'
+}

+ 20 - 4
src/main.ts

@@ -2,11 +2,27 @@ import { createApp } from 'vue';
 import App from './App.tsx';
 import router from './router/index';
 import dayjs from 'dayjs';
+// import { setupStore } from './store';
 import 'dayjs/locale/zh-cn';
 import './styles/index.less';
-const app = createApp(App);
 
-dayjs.locale('zh-ch');
+async function setupApp() {
+  // app loading
+  // const appLoading = createApp(AppLoading);
 
-app.use(router);
-app.mount('#app');
+  // appLoading.mount('#appLoading');
+
+  const app = createApp(App);
+
+  // store plugin: pinia
+  // setupStore(app);
+
+  dayjs.locale('zh-ch');
+
+  app.use(router);
+
+  // mount app
+  app.mount('#app');
+}
+
+setupApp();

+ 94 - 13
src/router/index.ts

@@ -1,22 +1,97 @@
+import { App } from 'vue';
 import {
   createRouter,
-  createWebHashHistory,
-  Router,
-  RouteRecordRaw
+  createWebHistory,
+  LocationQuery,
+  LocationQueryRaw,
+  LocationQueryValue,
+  Router
 } from 'vue-router';
+import { constantRoutes } from './routes/index';
+import { AesEncryption } from '@/utils/cipher';
+import { createRouterGuards } from './router-guards';
+const aes = new AesEncryption();
 
-export const LoginRoute: RouteRecordRaw = {
-  path: '/login',
-  name: 'Login',
-  component: () => import('@/views/login/index'),
-  meta: {
-    title: '登录'
-  }
-};
+/** Used as references for various `Number` constants. */
+const MAX_SAFE_INTEGER = 9007199254740991;
+function isLength(value: any) {
+  return (
+    typeof value === 'number' &&
+    value > -1 &&
+    value % 1 == 0 &&
+    value <= MAX_SAFE_INTEGER
+  );
+}
+function isUndefined(value: any) {
+  return value === undefined;
+}
+function isArray(value: any) {
+  return value != null && typeof value !== 'function' && isLength(value.length);
+}
+function isNull(value: any) {
+  return value === null;
+}
+/**
+ *
+ * @description 加密:反序列化字符串参数
+ */
+export function stringifyQuery(obj: LocationQueryRaw): string {
+  if (!obj) return '';
+  const result = Object.keys(obj)
+    .map(key => {
+      const value: any = obj[key];
+      if (isUndefined(value)) return '';
+      if (isNull(value)) return key;
+      if (isArray(value)) {
+        const resArray: string[] = [];
+        value.forEach((item: string) => {
+          if (isUndefined(item)) return;
+          if (isNull(item)) {
+            resArray.push(key);
+          } else {
+            resArray.push(key + '=' + item);
+          }
+        });
+        return resArray.join('&');
+      }
+      return `${key}=${value}`;
+    })
+    .filter(x => x.length > 0)
+    .join('&');
+  return result ? `?${aes.encryptByAES(result)}` : '';
+}
+
+/**
+ *
+ * @description 解密:反序列化字符串参数
+ */
+export function parseQuery(query: string): LocationQuery {
+  const res: LocationQuery = {};
+  query = query.trim().replace(/^(\?|#|&)/, '');
+  if (!query) return res;
+  query = aes.decryptByAES(query);
+  query.split('&').forEach(param => {
+    const parts = param.replace(/\+/g, ' ').split('=');
+    const key: any = parts.shift();
+    const val = parts.length > 0 ? parts.join('=') : null;
+    if (!isUndefined(key)) {
+      if (isUndefined(res[key])) {
+        res[key] = val;
+      } else if (isArray(res[key])) {
+        (res[key] as LocationQueryValue[]).push(val);
+      } else {
+        res[key] = [res[key] as LocationQueryValue, val];
+      }
+    }
+  });
+  return res;
+}
 
 const router: Router = createRouter({
-  history: createWebHashHistory(),
-  routes: [LoginRoute],
+  history: createWebHistory(),
+  routes: [...constantRoutes],
+  stringifyQuery,
+  parseQuery,
   scrollBehavior(to) {
     if (to.hash) {
       return {
@@ -27,4 +102,10 @@ const router: Router = createRouter({
   }
 });
 
+export function setupRouter(app: App) {
+  app.use(router);
+  // 创建路由守卫
+  createRouterGuards(router);
+}
+
 export default router;

+ 94 - 0
src/router/router-guards.ts

@@ -0,0 +1,94 @@
+import { isNavigationFailure, Router } from 'vue-router';
+// import { useUserStoreWidthOut } from '@/store/modules/user';
+// import { ACCESS_TOKEN } from '@/store/mutation-types';
+// import { storage } from '@/utils/storage';
+import { PageEnum } from '@/enums/pageEnum';
+import { useLoadingBar } from 'naive-ui';
+
+const LOGIN_PATH = PageEnum.BASE_LOGIN;
+
+const whitePathList = [LOGIN_PATH]; // no redirect whitelist
+
+const Loading = useLoadingBar();
+
+export function createRouterGuards(router: Router) {
+  // const userStore = useUserStoreWidthOut();
+  router.beforeEach(async (to, from, next) => {
+    Loading && Loading.start();
+    if (from.path === LOGIN_PATH && to.name === 'errorPage') {
+      next(PageEnum.BASE_HOME);
+      return;
+    }
+
+    // Whitelist can be directly entered
+    if (whitePathList.includes(to.path as PageEnum)) {
+      next();
+      return;
+    }
+
+    // const token = storage.get(ACCESS_TOKEN);
+
+    // if (!token) {
+    //   // You can access without permissions. You need to set the routing meta.ignoreAuth to true
+    //   if (to.meta.ignoreAuth) {
+    //     next();
+    //     return;
+    //   }
+    //   // redirect login page
+    //   const redirectData: { path: string; replace: boolean; query?: any } = {
+    //     path: LOGIN_PATH,
+    //     replace: true
+    //   };
+    //   if (to.path) {
+    //     redirectData.query = {
+    //       ...redirectData.query,
+    //       redirect: to.path
+    //     };
+    //   }
+    //   next(redirectData);
+    //   return;
+    // }
+
+    // await userStore.GetInfo();
+
+    // const redirectPath = (from.query.redirect || to.path) as string;
+    // const redirect = decodeURIComponent(redirectPath);
+    // const nextData =
+    //   to.path === redirect ? { ...to, replace: true } : { path: redirect };
+    // next(nextData);
+    next();
+    Loading && Loading.finish();
+  });
+
+  router.afterEach((to, _, failure) => {
+    if (isNavigationFailure(failure)) {
+      console.log('failed navigation', failure);
+    }
+    // const asyncRouteStore = useAsyncRouteStoreWidthOut();
+    // // 在这里设置需要缓存的组件名称
+    // const keepAliveComponents = asyncRouteStore.keepAliveComponents;
+    // const currentComName: any = to.matched.find(
+    //   item => item.name == to.name
+    // )?.name;
+    // if (
+    //   currentComName &&
+    //   !keepAliveComponents.includes(currentComName) &&
+    //   to.meta?.keepAlive
+    // ) {
+    //   // 需要缓存的组件
+    //   keepAliveComponents.push(currentComName);
+    // } else if (!to.meta?.keepAlive || to.name == 'Redirect') {
+    //   // 不需要缓存的组件
+    //   const index = asyncRouteStore.keepAliveComponents.findIndex(
+    //     name => name == currentComName
+    //   );
+    //   if (index != -1) {
+    //     keepAliveComponents.splice(index, 1);
+    //   }
+    // }
+    // asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
+    Loading && Loading.finish();
+  });
+
+  // router.onError(error => {});
+}

+ 29 - 0
src/router/routes/index.ts

@@ -0,0 +1,29 @@
+export const constantRoutes: any[] = [
+  {
+    name: 'login',
+    path: '/login',
+    component: () => import('@/views/login/index'),
+    meta: {
+      title: '登录',
+      singleLayout: 'blank'
+    }
+  },
+  {
+    name: '404',
+    path: '/404',
+    component: () => import('@/views/404/index'),
+    meta: {
+      title: '未找到',
+      singleLayout: 'blank'
+    }
+  },
+  // 匹配无效路径的路由
+  {
+    name: 'not-found',
+    path: '/:pathMatch(.*)*',
+    meta: {
+      title: '未找到',
+      singleLayout: 'blank'
+    }
+  }
+];

+ 10 - 0
src/store/index.ts

@@ -0,0 +1,10 @@
+import type { App } from 'vue';
+import { createPinia } from 'pinia';
+
+const store = createPinia();
+
+export function setupStore(app: App<Element>) {
+  app.use(store);
+}
+
+export { store };

+ 117 - 0
src/store/modules/users.ts

@@ -0,0 +1,117 @@
+import { defineStore } from 'pinia';
+import { store } from '@/store';
+import { ACCESS_TOKEN, CURRENT_USER, IM_TOKEN } from '@/store/mutation-types';
+import { storage } from '@/utils/storage';
+
+export interface IUserState {
+  token: string;
+  imToken: string;
+  username: string;
+  avatar: string;
+  info: any;
+}
+
+export const useUserStore = defineStore('user-store', {
+  state: (): IUserState => ({
+    token: storage.get(ACCESS_TOKEN, ''),
+    imToken: storage.get(IM_TOKEN, ''),
+    username: '',
+    avatar: '',
+    info: storage.get(CURRENT_USER, {})
+  }),
+  getters: {
+    getToken(): string {
+      return this.token;
+    },
+    getImToken(): string {
+      return this.imToken;
+    },
+    getAvatar(): string {
+      return this.avatar;
+    },
+    getNickname(): string {
+      return this.username;
+    },
+    getUserInfo(): object {
+      return this.info;
+    }
+  },
+  actions: {
+    setToken(token: string) {
+      this.token = token;
+    },
+    setImToken(token: string) {
+      this.imToken = token;
+    },
+    setAvatar(avatar: string) {
+      this.avatar = avatar;
+    },
+    setUserInfo(info: any) {
+      this.info = info;
+    },
+    // 登录
+    async login(userInfo: any) {
+      try {
+        // const response = (await login(userInfo)) as any;
+        // const { data, code } = response;
+        // if (code === ResultEnum.SUCCESS) {
+        const ex = 7 * 24 * 60 * 60 * 1000;
+        storage.set(ACCESS_TOKEN, 'bearer test1121121212121212', ex);
+        storage.set(
+          CURRENT_USER,
+          {
+            name: '王天奇',
+            avatar: ''
+          },
+          ex
+        );
+        //   this.setToken(data.token_type + ' ' + data.access_token);
+        //   this.setImToken(data.imToken);
+        //   this.setUserInfo(data);
+        // }
+        this.setToken('bearer test1121121212121212');
+        this.setUserInfo({
+          name: '王天奇',
+          avatar: ''
+        });
+        return Promise.resolve();
+      } catch (e) {
+        return Promise.reject(e);
+      }
+    },
+
+    // 获取用户信息
+    getInfo() {
+      // const that = this;
+      return new Promise((resolve, reject) => {
+        // getUserInfo()
+        //   .then(res => {
+        //     const result = res.data;
+        //     this..setUserInfo(result);
+        //     that.setAvatar(result.account.avatar);
+        //     resolve(res);
+        //   })
+        //   .catch(error => {
+        //     reject(error);
+        //   });
+        try {
+          resolve(true);
+        } catch {
+          reject();
+        }
+      });
+    },
+
+    // 登出
+    async logout() {
+      this.setUserInfo('');
+      storage.remove(ACCESS_TOKEN);
+      return Promise.resolve('');
+    }
+  }
+});
+
+// Need to be used outside the setup
+// export function useUserStoreWidthOut() {
+//   return useUserStore(store);
+// }

+ 4 - 0
src/store/mutation-types.ts

@@ -0,0 +1,4 @@
+export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
+export const IM_TOKEN = 'IM-TOKEN'; //
+export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
+export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页

+ 29 - 0
src/utils/cipher.ts

@@ -0,0 +1,29 @@
+import { encrypt, decrypt } from 'crypto-js/aes';
+import { parse } from 'crypto-js/enc-utf8';
+import pkcs7 from 'crypto-js/pad-pkcs7';
+import ECB from 'crypto-js/mode-ecb';
+import UTF8 from 'crypto-js/enc-utf8';
+// 注意 key 和 iv 至少都需要 16 位
+const AES_KEY = '1111111111000000';
+const AES_IV = '0000001111111111';
+export class AesEncryption {
+  private key;
+  private iv;
+  constructor(key = AES_KEY, iv = AES_IV) {
+    this.key = parse(key);
+    this.iv = parse(iv);
+  }
+  get getOptions() {
+    return {
+      mode: ECB,
+      padding: pkcs7,
+      iv: this.iv
+    };
+  }
+  encryptByAES(text: string) {
+    return encrypt(text, this.key, this.getOptions).toString();
+  }
+  decryptByAES(text: string) {
+    return decrypt(text, this.key, this.getOptions).toString(UTF8);
+  }
+}

+ 134 - 0
src/utils/storage.ts

@@ -0,0 +1,134 @@
+// 默认缓存期限为7天
+const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
+
+/**
+ * 创建本地缓存对象
+ * @param {string=} prefixKey -
+ * @param {Object} [storage=localStorage] - sessionStorage | localStorage
+ */
+export const createStorage = ({
+  prefixKey = '',
+  storage = localStorage
+} = {}) => {
+  /**
+   * 本地缓存类
+   * @class Storage
+   */
+  const Storage = class {
+    private storage = storage;
+    private prefixKey?: string = prefixKey;
+
+    private getKey(key: string) {
+      return `${this.prefixKey}${key}`.toUpperCase();
+    }
+
+    /**
+     * @description 设置缓存
+     * @param {string} key 缓存键
+     * @param {*} value 缓存值
+     * @param expire
+     */
+    set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
+      const stringData = JSON.stringify({
+        value,
+        expire: expire !== null ? new Date().getTime() + expire * 1000 : null
+      });
+      this.storage.setItem(this.getKey(key), stringData);
+    }
+
+    /**
+     * 读取缓存
+     * @param {string} key 缓存键
+     * @param {*=} def 默认值
+     */
+    get(key: string, def: any = null) {
+      const item = this.storage.getItem(this.getKey(key));
+      if (item) {
+        try {
+          const data = JSON.parse(item);
+          const { value, expire } = data;
+          // 在有效期内直接返回
+          if (expire === null || expire >= Date.now()) {
+            return value;
+          }
+          this.remove(key);
+        } catch (e) {
+          return def;
+        }
+      }
+      return def;
+    }
+
+    /**
+     * 从缓存删除某项
+     * @param {string} key
+     */
+    remove(key: string) {
+      this.storage.removeItem(this.getKey(key));
+    }
+
+    /**
+     * 清空所有缓存
+     * @memberOf Cache
+     */
+    clear(): void {
+      this.storage.clear();
+    }
+
+    /**
+     * 设置cookie
+     * @param {string} name cookie 名称
+     * @param {*} value cookie 值
+     * @param {number=} expire 过期时间
+     * 如果过期时间未设置,默认关闭浏览器自动删除
+     * @example
+     */
+    setCookie(
+      name: string,
+      value: any,
+      expire: number | null = DEFAULT_CACHE_TIME
+    ) {
+      document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
+    }
+
+    /**
+     * 根据名字获取cookie值
+     * @param name
+     */
+    getCookie(name: string): string {
+      const cookieArr = document.cookie.split('; ');
+      for (let i = 0, length = cookieArr.length; i < length; i++) {
+        const kv = cookieArr[i].split('=');
+        if (kv[0] === this.getKey(name)) {
+          return kv[1];
+        }
+      }
+      return '';
+    }
+
+    /**
+     * 根据名字删除指定的cookie
+     * @param {string} key
+     */
+    removeCookie(key: string) {
+      this.setCookie(key, 1, -1);
+    }
+
+    /**
+     * 清空cookie,使所有cookie失效
+     */
+    clearCookie(): void {
+      const keys = document.cookie.match(/[^ =;]+(?==)/g);
+      if (keys) {
+        for (let i = keys.length; i--; ) {
+          document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
+        }
+      }
+    }
+  };
+  return new Storage();
+};
+
+export const storage = createStorage();
+
+export default Storage;

+ 0 - 0
src/views/404/index.module.less


+ 8 - 0
src/views/404/index.tsx

@@ -0,0 +1,8 @@
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: '404-page',
+  setup() {
+    return () => <>404</>;
+  }
+});

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott