[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\nnot ie 11\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".env-config.ts",
    "content": "/** 请求服务的环境配置 */\ntype ServiceEnv = Record<ServiceEnvType, ServiceEnvConfig>;\n\n/** 不同请求服务的环境配置 */\nconst serviceEnv: ServiceEnv = {\n  dev: {\n    url: 'http://localhost:8080',\n    urlPattern: '/url-pattern',\n    secondUrl: 'http://localhost:8081',\n    secondUrlPattern: '/second-url-pattern'\n  },\n  test: {\n    url: 'http://localhost:8080',\n    urlPattern: '/url-pattern',\n    secondUrl: 'http://localhost:8081',\n    secondUrlPattern: '/second-url-pattern'\n  },\n  prod: {\n    url: 'http://localhost:8080',\n    urlPattern: '/url-pattern',\n    secondUrl: 'http://localhost:8081',\n    secondUrlPattern: '/second-url-pattern'\n  }\n};\n\n/**\n * 获取当前环境模式下的请求服务的配置\n * @param env 环境\n */\nexport function getServiceEnvConfig(env: ImportMetaEnv) {\n  const { VITE_SERVICE_ENV = 'dev' } = env;\n\n  const config = serviceEnv[VITE_SERVICE_ENV];\n\n  return config;\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\nsrc/typings/components.d.ts\nsrc/typings/auto-imports.d.ts\n/yarn.lock\n"
  },
  {
    "path": "Makefile",
    "content": "ImageTag ?=v1.1.0\nImageName ?= sunhao1256/lulu-admin-frontend:$(ImageTag)\nPlatform ?=linux/amd64\n\nVERSION=$(shell git rev-parse --short HEAD)\n\nall: compile docker-build docker-push\n\ncompile:\n\tyarn build-no-typecheck\n\ndocker-build:\n\tdocker build --platform=$(Platform) --build-arg version=$(VERSION) -t ${ImageName} -f docker/Dockerfile .\n\ndocker-push:\n\tdocker push ${ImageName}\n"
  },
  {
    "path": "README.md",
    "content": "# Lulu Admin\n\nelegant admin template, based on Vue3 , TypeScript, Vuetify3, Axios\n\nform-generator , bpmn-js-camunda\n\n## Lulu\nLulu is my arrogant cat\n<img src=\"https://i.imgur.com/3e63lxL.jpg\" style=\"zoom:8%;\" />\n\n## Feature\n\n- bpmn-js-camunda\n- form-generator 😄\n- chat\n\n[Vue3](https://vuejs.org/guide/quick-start.html#creating-a-vue-application)\n\n[Vuetify3](https://next.vuetifyjs.com/en/getting-started/installation/)\n\n- Dialog Provider\n- SnackBar Provider\n- LoadingOverly Provider\n\nTypescript\n\nTsx\n\nAxios\n\n- full Axios Request Stack Example\n- thanks to  [SoybeanAdmin](https://github.com/honghuangdc/soybean-admin) Perfect Project 👍\n\n[Pinia](https://pinia.vuejs.org/)\n\n\n\n## Caution!\n\n[vite-plugin-mock](https://github.com/vbenjs/vite-plugin-mock) instead of server api actually\n## Reference\n\ninfrastructure [SoybeanAdmin](https://github.com/honghuangdc/soybean-admin)\n\n**ui plagiarize** [luxAdminPro](https://lux-admin-pro.indielayer.com/dashboard/analytics)\n- if you want more features, please support genuine\n\n[native-ui](https://github.com/tusen-ai/naive-ui)\n\n[vben](https://github.com/vbenjs)\n## ScreenShot\n\n![](https://i.imgur.com/RQinDIf.png)\n![](https://i.imgur.com/wmODutf.png)\n![](https://i.imgur.com/S5HeYO2.png)\n![](https://i.imgur.com/MgHU7Av.png)\n![](https://i.imgur.com/Xr5gqgE.png)\n![](https://i.imgur.com/OVjed1u.png)\n![](https://i.imgur.com/bNXRsiv.png)\n\n\n\n## Project setup\n\n```\n# yarn\nyarn\n\n# npm\nnpm install\n\n# pnpm\npnpm install\n```\n\n### Compiles and hot-reloads for development\n\n```\n# yarn\nyarn dev\n\n# npm\nnpm run dev\n\n# pnpm\npnpm dev\n```\n\n### Compiles and minifies for production\n\n```\n# yarn\nyarn build\n\n# npm\nnpm run build\n\n# pnpm\npnpm build\n```\n\n### Customize configuration\n\nSee [Configuration Reference](https://vitejs.dev/config/).\n"
  },
  {
    "path": "build/index.ts",
    "content": "export * from './plugins';\nexport * from './utils';\n"
  },
  {
    "path": "build/plugins/index.ts",
    "content": "import unplugin from \"./unplugin\";\nimport vuetify from \"./vuetify\";\nimport vue from '@vitejs/plugin-vue'\nimport mock from './mock';\nimport type {PluginOption} from 'vite';\n\nexport function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | PluginOption[])[] {\n  return [vue(), vuetify(), ...unplugin(viteEnv), mock(viteEnv)];\n}\n"
  },
  {
    "path": "build/plugins/mock.ts",
    "content": "import {viteMockServe} from 'vite-plugin-mock';\n\nexport default (env: ImportMetaEnv) => {\n  const prodMock = true\n  const {VITE_SERVICE_ENV = 'dev'} = env;\n  return viteMockServe({\n    mockPath: 'mock',\n    localEnabled: VITE_SERVICE_ENV === 'dev',\n    prodEnabled: VITE_SERVICE_ENV === 'prod' && prodMock,\n    injectCode: `\n\t\timport { setupMockServer } from '../mock';\n\t\tsetupMockServer();\n\t`\n  });\n}\n"
  },
  {
    "path": "build/plugins/unplugin.ts",
    "content": "import Components from 'unplugin-vue-components/vite';\nimport AutoImport from 'unplugin-auto-import/vite';\nimport Icons from 'unplugin-icons/vite'\nimport {getSrcPath} from '../utils';\nimport {FileSystemIconLoader} from \"unplugin-icons/loaders\";\nimport IconsResolver from 'unplugin-icons/resolver'\nimport {createSvgIconsPlugin} from 'vite-plugin-svg-icons';\n\nimport vueJsx from '@vitejs/plugin-vue-jsx'\n\n\nexport default function unplugin(viteEnv: ImportMetaEnv) {\n  const {VITE_ICON_PREFFIX} = viteEnv;\n\n  const srcPath = getSrcPath();\n  const localIconPath = `${srcPath}/assets/images/svgs`;\n\n  /** 本地svg图标集合名称 */\n  const collectionName = 'local';\n\n  return [\n    AutoImport({\n      imports: ['vue', {\n        'vuetify': ['useTheme'],\n        'pinia': ['storeToRefs'],\n      }],\n      dirs: [`${srcPath}/plugins`, `${srcPath}/filters`, `${srcPath}/store/**`, `${srcPath}/router`],\n      resolvers: [\n        IconsResolver({\n          customCollections: [collectionName],\n          componentPrefix: VITE_ICON_PREFFIX,\n          enabledCollections: [collectionName],\n        }),\n\n      ],\n      dts: \"src/typings/auto-imports.d.ts\",\n    }),\n    Icons(\n      {\n        compiler: 'vue3',\n        customCollections: {\n          [collectionName]: FileSystemIconLoader(localIconPath)\n        }\n      }\n    ),\n    Components({\n      dts: 'src/typings/components.d.ts',\n      dirs: ['src/components'],\n      extensions: ['vue'],\n      resolvers: [\n        IconsResolver(\n          {\n            customCollections: [collectionName],\n            prefix: VITE_ICON_PREFFIX\n          }\n        ),\n        (componentName) => {\n          // where `componentName` is always CapitalCase\n          if (componentName.startsWith('V'))\n            return {name: componentName, from: 'vuetify/lib/labs/components'}\n        },\n\n        (componentName) => {\n          // where `componentName` is always CapitalCase\n          if (componentName.endsWith('Provider'))\n            return {name: componentName, from: '@/components/provider'}\n        }\n      ]\n    }),\n    createSvgIconsPlugin({\n      iconDirs: [localIconPath],\n      symbolId: `${VITE_ICON_PREFFIX}-[dir]-[name]`,\n      inject: 'body-last',\n      customDomId: '__SVG_ICON_LOCAL__'\n    }),\n    vueJsx({optimize: false, enableObjectSlots: true}),\n\n  ];\n}\n"
  },
  {
    "path": "build/plugins/vuetify.ts",
    "content": "import vuetify from 'vite-plugin-vuetify'\n\nexport default function vuetifyPlugin() {\n  return vuetify({\n    autoImport: true,\n    styles: {\n      configFile: 'src/assets/scss/settings.scss'\n    }\n  })\n\n}\n"
  },
  {
    "path": "build/utils/index.ts",
    "content": "import path from 'path';\n\n/**\n * 获取项目根路径\n * @descrition 末尾不带斜杠\n */\nexport function getRootPath() {\n  return path.resolve(process.cwd());\n}\n\n/**\n * 获取项目src路径\n * @param srcName - src目录名称(默认: \"src\")\n * @descrition 末尾不带斜杠\n */\nexport function getSrcPath(srcName = 'src') {\n  const rootPath = getRootPath();\n\n  return `${rootPath}/${srcName}`;\n}\n"
  },
  {
    "path": "docker/.dockerignore",
    "content": "node_modules\n.DS_Store\ndist\n.npmrc\n.cache\n\ntests/server/static\ntests/server/static/upload\n\n.local\n# local env files\n.env.local\n.env.*.local\n.eslintcache\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n# .vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\nyarn.lock\npnpm-lock.yaml\n/vite-profile.cpuprofile\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "FROM nginx:alpine as prod\n\nENV WORKDIR=/lulu-admin\n\nWORKDIR $WORKDIR\n\nARG version\nENV COMMITID=$version\n\n\nCOPY /dist /lulu-admin\nCOPY /docker/nginx.conf /etc/nginx/nginx.conf\n\nEXPOSE 80\n"
  },
  {
    "path": "docker/nginx.conf",
    "content": "user  nginx;\nworker_processes  1;\nerror_log  /var/log/nginx/error.log warn;\npid        /var/run/nginx.pid;\n\nevents {\n  worker_connections  1024;\n}\n\nhttp {\n  include       /etc/nginx/mime.types;\n  default_type  application/octet-stream;\n  log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                    '$status $body_bytes_sent \"$http_referer\" '\n                    '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n  access_log  /var/log/nginx/access.log  main;\n  sendfile        on;\n  keepalive_timeout  65;\n\n  server {\n    listen       80;\n    server_name  localhost;\n\n    location / {\n      # 不缓存html，防止程序更新后缓存继续生效\n      if ($request_filename ~* .*\\.(?:htm|html)$) {\n        add_header Cache-Control \"private, no-store, no-cache, must-revalidate, proxy-revalidate\";\n        access_log on;\n      }\n      root   /lulu-admin/;\n      index  index.html index.htm;\n      try_files $uri $uri/ /index.html;\n    }\n\n    error_page   500 502 503 504  /50x.html;\n    location = /50x.html {\n      root   /usr/share/nginx/html;\n    }\n  }\n}\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<!--    <link href=\"https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">-->\n    <title>Lulu Admin</title>\n</head>\n\n<body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "mock/api/auth.ts",
    "content": "import type {MockMethod} from 'vite-plugin-mock';\nimport {userModel} from '../model';\nimport {mock} from \"mockjs\";\n\nconst apis: MockMethod[] = [\n  {\n    url: '/mock/getSmsCode',\n    method: 'post',\n    response: (): Service.MockServiceResult<boolean> => {\n      return {\n        code: 200,\n        message: 'ok',\n        data: true\n      };\n    }\n  },\n  {\n    url: '/mock/login',\n    method: 'post',\n    timeout: 500,\n    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {\n      const {userName = undefined, password = undefined} = options.body;\n\n      if (!userName || !password) {\n        return {\n          code: 400,\n          message: \"request parameter invalid\",\n          data: null\n        };\n      }\n\n      const findItem = userModel.find(item => item.userName === userName && item.password === password);\n\n      if (findItem) {\n        return {\n          code: 200,\n          message: 'ok',\n          data: {\n            token: findItem.token,\n            refreshToken: findItem.refreshToken\n          }\n        };\n      }\n      return {\n        code: 1000,\n        message: '用户名或密码错误！',\n        data: null\n      };\n    }\n  },\n  {\n    url: '/mock/getUserInfo',\n    method: 'get',\n    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.UserInfo | null> => {\n      const {authorization = ''} = options.headers;\n      const REFRESH_TOKEN_CODE = 66666;\n\n      if (!authorization) {\n        return {\n          code: REFRESH_TOKEN_CODE,\n          message: '用户已失效或不存在！',\n          data: null\n        };\n      }\n      const userInfo: Auth.UserInfo = {\n        userId: '',\n        userName: '',\n        userRole: 'user',\n        userAvatar: 'avatar' + mock({\n          \"number|1-20\": 2\n        })['number']\n\n      };\n      const isInUser = userModel.some(item => {\n        const flag = item.token === authorization;\n        if (flag) {\n          const {userId: itemUserId, userName, userRole} = item;\n          Object.assign(userInfo, {userId: itemUserId, userName, userRole});\n        }\n        return flag;\n      });\n\n      if (isInUser) {\n        return {\n          code: 200,\n          message: 'ok',\n          data: userInfo\n        };\n      }\n\n      return {\n        code: REFRESH_TOKEN_CODE,\n        message: '用户信息异常！',\n        data: null\n      };\n    }\n  },\n  {\n    url: '/mock/updateToken',\n    method: 'post',\n    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {\n      const {refreshToken = ''} = options.body;\n\n      const findItem = userModel.find(item => item.refreshToken === refreshToken);\n\n      if (findItem) {\n        return {\n          code: 200,\n          message: 'ok',\n          data: {\n            token: findItem.token,\n            refreshToken: findItem.refreshToken\n          }\n        };\n      }\n      return {\n        code: 3000,\n        message: '用户已失效或不存在！',\n        data: null\n      };\n    }\n  }\n];\n\nexport default apis;\n"
  },
  {
    "path": "mock/api/chat.ts",
    "content": "import {mock} from 'mockjs';\nimport type {MockMethod} from 'vite-plugin-mock';\n\nconst apis: MockMethod[] = [\n  {\n    url: '/mock/getMessage',\n    method: 'post',\n    response: (): Service.MockServiceResult<ApiChatManagement.message> => {\n\n      const data = mock({\n        id: '@id',\n        text: '@sentence',\n        timestamp: '@datetime',\n        user: {\n          id: '@id',\n          avatar: () => {\n            return 'avatar' + mock({\n              \"number|1-20\": 2\n            })['number']\n          }\n        }\n      })\n\n      return {\n        code: 200,\n        message: 'ok',\n        data\n      };\n    }\n  },\n];\n\nexport default apis;\n"
  },
  {
    "path": "mock/api/index.ts",
    "content": "import auth from './auth';\nimport chat from './chat';\nimport route from './route';\nimport management from './management';\n\nexport default [...management, ...auth, ...route, ...chat];\n"
  },
  {
    "path": "mock/api/management.ts",
    "content": "import {mock} from 'mockjs';\nimport type {MockMethod} from 'vite-plugin-mock';\n\nconst apis: MockMethod[] = [\n  {\n    url: '/mock/getAllUserList',\n    method: 'post',\n    timeout: 1000,\n    response: (): Service.MockServiceResult<ApiCommon.PageResult<ApiUserManagement.User[]>> => {\n      const data = mock({\n        pageNo: 1,\n        pageSize: 10,\n        total: 20,\n        'list|10': [\n          {\n            id: '@id',\n            name: '@name',\n            'role|1': ['user', 'admin'],\n            'age|18-56': 56,\n            'gender|1': ['0', '1', null],\n            \"verified|1-2\": true,\n            'created': mock('@datetime()'),\n            'lastSignIn': mock('@datetime()'),\n            phone:\n              /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/,\n            'email|1': ['@email(\"gmail.com\")'],\n            'userStatus|1': ['1', '2', '3', '4', null],\n            'birthDay': mock('@date()'),\n            avatar: () => {\n              return 'avatar' + mock({\n                \"number|1-20\": 2\n              })['number']\n            }\n          }\n        ],\n      });\n      return {\n        code: 200,\n        message: 'ok',\n        data: data\n      };\n    }\n  },\n\n  {\n    url: '/mock/getUser/:id?',\n    method: 'post',\n    timeout: 1500,\n    response: (): Service.MockServiceResult<ApiUserManagement.User> => {\n      const data = mock({\n        id: '@id',\n        name: '@name',\n        'role|1': ['user', 'admin'],\n        'age|18-56': 56,\n        'gender|1': ['0', '1', null],\n        \"verified|1-2\": true,\n        'created': mock('@datetime()'),\n        'lastSignIn': mock('@datetime()'),\n        phone:\n          /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/,\n        'email|1': ['@email(\"qq.com\")'],\n        'userStatus|1': ['1', '2', '3', '4', null],\n        'birthDay': mock('@date()'),\n        avatar: () => {\n          return 'avatar' + mock({\n            \"number|1-20\": 2\n          })['number']\n        }\n      });\n      return {\n        code: 200,\n        message: 'ok',\n        data: data\n      };\n    }\n  },\n\n\n  {\n    url: '/mock/getAllFormList',\n    method: 'post',\n    timeout: 1000,\n    response: (): Service.MockServiceResult<ApiCommon.PageResult<ApiForm.Form[]>> => {\n      const data = mock({\n        pageNo: 1,\n        pageSize: 10,\n        total: 20,\n        'list|10': [\n          {\n            id: '@id',\n            name: '@name',\n            'created': mock('@datetime()'),\n            'status|1': ['1', '0'],\n          }\n        ],\n      });\n      return {\n        code: 200,\n        message: 'ok',\n        data: data\n      };\n    }\n  },\n];\n\nexport default apis;\n"
  },
  {
    "path": "mock/api/route.ts",
    "content": "import type {MockMethod} from 'vite-plugin-mock';\nimport {routeModel, userModel} from '../model';\n\nconst apis: MockMethod[] = [\n  {\n    url: '/mock/getUserRoutes',\n    method: 'post',\n    response: (options: Service.MockOption): Service.MockServiceResult => {\n      const {userId = undefined} = options.body;\n\n      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'dashboard_analytics';\n\n      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';\n\n      const filterRoutes = routeModel[role];\n\n      return {\n        code: 200,\n        message: 'ok',\n        data: {\n          routes: filterRoutes,\n          home: routeHomeName\n        }\n      };\n    }\n  }\n];\n\nexport default apis;\n"
  },
  {
    "path": "mock/index.ts",
    "content": "import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';\nimport api from './api';\n\nexport function setupMockServer() {\n  createProdMockServer(api);\n}\n"
  },
  {
    "path": "mock/model/auth.ts",
    "content": "interface UserModel extends Auth.UserInfo {\n  token: string;\n  refreshToken: string;\n  password: string;\n}\n\nexport const userModel: UserModel[] = [\n  {\n    token: '__TOKEN_ADMIN__',\n    refreshToken: '__REFRESH_TOKEN_ADMIN__',\n    userId: '2',\n    userName: 'admin',\n    userRole: 'admin',\n    password: 'admin123'\n  },\n  {\n    token: '__TOKEN_USER01__',\n    refreshToken: '__REFRESH_TOKEN_USER01__',\n    userId: '3',\n    userName: 'user01',\n    userRole: 'user',\n    password: 'user01123'\n  }\n];\n"
  },
  {
    "path": "mock/model/index.ts",
    "content": "export * from './auth';\nexport * from './route';\n"
  },
  {
    "path": "mock/model/route.ts",
    "content": "export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {\n  admin: [\n    {\n      name: 'dashboard',\n      path: '/dashboard',\n      component: 'basic',\n      children: [\n        {\n          name: 'dashboard_analytics',\n          path: '/dashboard/analytics',\n          component: 'self',\n          meta: {\n            icon: 'mdi-view-dashboard-outline',\n            title: 'menu.dashboard',\n            requiresAuth: true\n          }\n        }\n      ],\n      meta: {\n        title: 'menu.dashboard',\n        icon: 'mdi-view-dashboard-outline',\n        order: 1,\n        requiresAuth: true\n      }\n    },\n    {\n      name: 'apps',\n      path: '/apps',\n      component: 'basic',\n      children: [\n        {\n          name: 'apps_manager-user',\n          path: '/apps/manager-user',\n          component: 'blank',\n          children: [\n            {\n              name: 'apps_manager-user_list',\n              path: '/apps/manager-user/list',\n              component: 'self',\n              meta: {\n                title: 'menu.usersList',\n                requiresAuth: true\n              }\n            },\n            {\n              name: 'apps_manager-user_edit',\n              path: '/apps/manager-user/edit',\n              component: 'self',\n              meta: {\n                title: 'menu.usersEdit',\n                dynamicPath: '/apps/manager-user/edit/:id?',\n                requiresAuth: true\n              }\n            }\n          ],\n          meta: {\n            title: 'menu.users',\n            icon: 'mdi-account-multiple-outline',\n            requiresAuth: true,\n            order: 2,\n          }\n        },\n        {\n          name: 'apps_board',\n          path: '/apps/board',\n          component: 'self',\n          meta: {\n            title: 'menu.board',\n            icon: 'mdi-view-column-outline',\n            order: 1,\n            requiresAuth: true,\n          }\n        },\n        {\n          name: 'apps_todo',\n          path: '/apps/todo',\n          component: 'todo',\n          children: [\n            {\n              name: 'apps_todo_tasks',\n              path: '/apps/todo/tasks',\n              component: 'self',\n              meta: {\n                title: 'todo.tasks',\n                requiresAuth: true,\n                hide: true,\n              }\n\n            },\n            {\n              name: 'apps_todo_completed',\n              path: '/apps/todo/completed',\n              component: 'self',\n              meta: {\n                title: 'todo.completed',\n                requiresAuth: true,\n                hide: true,\n              }\n            },\n            {\n              name: 'apps_todo_label',\n              path: '/apps/todo/label',\n              component: 'self',\n              meta: {\n                title: 'todo.labels',\n                hide: true,\n                requiresAuth: true,\n                dynamicPath: '/apps/todo/label/:id'\n              }\n\n            }\n          ],\n          meta: {\n            title: 'menu.todo',\n            icon: 'mdi-format-list-checkbox',\n            requiresAuth: true,\n            order: 1\n          }\n        },\n        {\n          name: \"apps_chat\",\n          path: \"/apps/chat\",\n          component: \"chat\",\n          children: [\n            {\n              name: 'apps_chat-channel',\n              path: '/apps/chat-channel',\n              component: 'self',\n              meta: {\n                title: 'menu.chat-channel',\n                dynamicPath: '/apps/chat-channel/:id?',\n                hide: true\n              }\n            }\n          ],\n          meta: {\n            title: \"menu.chat\",\n            order: 1,\n            icon: \"mdi-forum-outline\"\n          }\n        }\n      ],\n      meta: {\n        title: 'menu.apps',\n        requiresAuth: true,\n      }\n    },\n    {\n      name: 'other',\n      path: '/other',\n      component: 'basic',\n      meta: {\n        title: 'menu.others'\n      },\n      children: [\n        {\n          name: 'blank-page',\n          path: '/blank-page',\n          component: 'blank',\n          meta: {\n            title: 'menu.blank',\n            icon: 'mdi-file-outline',\n          }\n        },\n        {\n          name: 'other_menu-levels',\n          component: 'blank',\n          path: '/other/menu-levels',\n          meta: {\n            title: 'menu.levels'\n          },\n          children: [\n            {\n              name: 'other_menu-levels-2-1',\n              path: '/other/menu-levels-2-1',\n              component: 'self',\n              meta: {\n                title: 'menu.levels2-1'\n              }\n            },\n            {\n              name: 'other_menu-levels-2-2',\n              path: '/other/menu-levels-2-2',\n              component: 'blank',\n              meta: {\n                title: 'menu.levels2-2',\n              },\n              children: [\n                {\n                  name: 'other_menu-levels-3-1',\n                  path: '/other/menu-levels-3-1',\n                  component: 'self',\n                  meta: {\n                    title: 'menu.levels3-1'\n                  }\n                },\n                {\n                  name: 'other_menu-levels-3-2',\n                  path: '/other/menu-levels-3-2',\n                  component: 'self',\n                  meta: {\n                    title: 'menu.levels3-2'\n                  }\n                }\n              ]\n            }\n          ]\n        },\n\n      ]\n    },\n\n\n    {\n      name: \"flowable\",\n      path: \"/flowable\",\n      component: 'basic',\n      children: [\n        {\n          name: 'flowable_design',\n          path: '/flowable/design',\n          component: 'self',\n          meta: {\n            title: 'menu.flowable-design',\n            icon: \"mdi-pencil\"\n          }\n        },\n\n        {\n          name: 'form_list',\n          path: '/form/list',\n          component: 'self',\n          meta: {\n            title: 'menu.form-list',\n          }\n        },\n        {\n          name: 'form_design',\n          path: '/form/design',\n          component: 'self',\n          meta: {\n            title: 'menu.form-design',\n          }\n        },\n      ],\n      meta: {\n        title: \"menu.flowable\",\n        icon: \"mdi-waves-arrow-right\"\n      }\n    },\n\n  ],\n  user: [\n    {\n      name: 'dashboard',\n      path: '/dashboard',\n      component: 'basic',\n      children: [\n        {\n          name: 'dashboard_analytics',\n          path: '/dashboard/analytics',\n          component: 'self',\n          meta: {\n            icon: 'mdi-view-dashboard-outline',\n            title: 'menu.dashboard',\n            requiresAuth: true\n          }\n        }\n      ],\n      meta: {\n        title: 'menu.dashboard',\n        icon: 'mdi-view-dashboard-outline',\n        order: 1,\n        requiresAuth: true\n      }\n    },\n  ]\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lulu-admin\",\n  \"version\": \"1.1.0\",\n  \"homepage\": \"https://github.com/sunhao1256/lulu-admin\",\n  \"repository\": {\n    \"url\": \"https://github.com/sunhao1256/lulu-admin.git\"\n  },\n  \"scripts\": {\n    \"dev\": \"cross-env VITE_SERVICE_ENV=dev vite\",\n    \"build\": \"npm run typecheck && cross-env VITE_SERVICE_ENV=prod vite build \",\n    \"build-no-typecheck\": \"cross-env VITE_SERVICE_ENV=prod vite build \",\n    \"preview\": \"vite preview\",\n    \"typecheck\": \"vue-tsc --noEmit --skipLibCheck\"\n  },\n  \"dependencies\": {\n    \"@bpmn-io/properties-panel\": \"^1.4.0\",\n    \"@mdi/font\": \"7.0.96\",\n    \"@vueuse/core\": \"^9.12.0\",\n    \"animate.css\": \"^4.1.1\",\n    \"apexcharts\": \"^3.36.3\",\n    \"axios\": \"0.27.2\",\n    \"bpmn-js\": \"^11.5.0\",\n    \"bpmn-js-properties-panel\": \"^1.19.1\",\n    \"camunda-bpmn-moddle\": \"^7.0.1\",\n    \"codemirror\": \"^6.0.1\",\n    \"crypto-js\": \"^4.1.1\",\n    \"flag-icons\": \"^6.6.6\",\n    \"lodash-es\": \"^4.17.21\",\n    \"moment\": \"^2.29.4\",\n    \"pinia\": \"^2.0.28\",\n    \"qs\": \"^6.11.0\",\n    \"seemly\": \"^0.3.6\",\n    \"ua-parser-js\": \"^1.0.33\",\n    \"vooks\": \"^0.2.12\",\n    \"vue\": \"^3.2.47\",\n    \"vue-codemirror\": \"^6.1.1\",\n    \"vue-i18n\": \"^9.2.2\",\n    \"vue-router\": \"^4.1.6\",\n    \"vue3-apexcharts\": \"^1.4.1\",\n    \"vuedraggable\": \"^4.1.0\",\n    \"vuetify\": \"3.1.7\",\n    \"webfontloader\": \"^1.0\"\n  },\n  \"devDependencies\": {\n    \"@types/crypto-js\": \"^4.1.1\",\n    \"@types/lodash-es\": \"^4.17.6\",\n    \"@types/node\": \"^18.11.9\",\n    \"@types/qs\": \"^6.9.7\",\n    \"@types/ua-parser-js\": \"^0.7.36\",\n    \"@types/webfontloader\": \"^1.6.35\",\n    \"@vitejs/plugin-vue\": \"^3.0.3\",\n    \"@vitejs/plugin-vue-jsx\": \"^3.0.0\",\n    \"@vue/babel-plugin-jsx\": \"^1.1.1\",\n    \"cross-env\": \"^7.0.3\",\n    \"mockjs\": \"^1.1.0\",\n    \"sass\": \"^1.58.3\",\n    \"sass-loader\": \"^13.2.0\",\n    \"typescript\": \"^4.0.0\",\n    \"unplugin-auto-import\": \"^0.12.1\",\n    \"unplugin-icons\": \"^0.15.1\",\n    \"unplugin-vue-components\": \"^0.22.12\",\n    \"vite\": \"^3.0.9\",\n    \"vite-plugin-mock\": \"^2.9.6\",\n    \"vite-plugin-svg-icons\": \"^2.0.1\",\n    \"vite-plugin-vuetify\": \"^1.0.0-alpha.12\",\n    \"vue-tsc\": \"^1.0.9\"\n  }\n}\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <v-app>\n    <vuetify-provider>\n      <router-view/>\n    </vuetify-provider>\n  </v-app>\n</template>\n\n<script setup lang=\"ts\">\nimport {useGlobalEvents} from \"@/composables/events\";\nimport {subscribeStore} from '@/store';\n\n\nsubscribeStore()\nuseGlobalEvents();\n</script>\n"
  },
  {
    "path": "src/assets/scss/settings.css",
    "content": "/* Error: @use rules must be written before any other rules.\n *   ,\n * 2 | @use \"vuetify/settings\";\n *   | ^^^^^^^^^^^^^^^^^^^^^^^\n *   '\n *   settings.scss 2:1  root stylesheet */\n\nbody::before {\n  font-family: \"Source Code Pro\", \"SF Mono\", Monaco, Inconsolata, \"Fira Mono\",\n      \"Droid Sans Mono\", monospace, monospace;\n  white-space: pre;\n  display: block;\n  padding: 1em;\n  margin-bottom: 1em;\n  border-bottom: 2px solid black;\n  content: 'Error: @use rules must be written before any other rules.\\a   \\2577 \\a 2 \\2502  @use \"vuetify/settings\";\\a   \\2502  ^^^^^^^^^^^^^^^^^^^^^^^\\a   \\2575 \\a   settings.scss 2:1  root stylesheet';\n}\n"
  },
  {
    "path": "src/assets/scss/settings.scss",
    "content": "@use \"vuetify/variables\";\n@use \"vuetify/settings\" with (\n$spacer: variables.$spacer,\n$body-font-family: variables.$body-font-family,\n$font-size-root: variables.$font-size-root,\n$font-weights: variables.$font-weights,\n$container-padding-x: 24px,\n\n//nav\n$list-item-nav-title-font-size:0.875rem,\n$list-item-nav-title-font-weight:600,\n$list-item-nav-margin-top:0,\n\n//icon\n\n$icon-sizes: (\n    'default': 1.6em,\n),\n$button-icon-density: (\n 'default': 1.5,\n 'comfortable': 0,\n 'compact': -1\n),\n\n\n//elevations\n$shadow-key-umbra-opacity:variables.$shadow-key-umbra-opacity,\n$shadow-key-penumbra-opacity:variables.$shadow-key-penumbra-opacity,\n$shadow-key-ambient-opacity:variables.$shadow-key-ambient-opacity,\n$shadow-key-umbra:variables.$shadow-key-umbra,\n$shadow-key-penumbra:variables.$shadow-key-penumbra,\n$shadow-key-ambient:variables.$shadow-key-ambient,\n\n\n//card\n$card-title-padding: variables.$spacer * 2 variables.$spacer * 2,\n$border-radius-root: 6px,\n\n\n//timeline\n$timeline-dot-size: 34px,\n\n//table\n$table-row-height: 48px,\n\n$typography: (\n    'button': (\n      'text-transform': none,\n      'weight': 700,\n\n    ),\n\n),\n$list-item-title-font-weight: 600,\n$field-font-size: 14px\n\n)\n;\n\n@forward \"vuetify/settings\";\n"
  },
  {
    "path": "src/assets/scss/theme.css",
    "content": "/**\n * Vuetify Styles Overrides\n */\n.v-application .d-flex {\n  min-width: 0;\n}\n\n.v-application p {\n  margin-bottom: 20px;\n}\n\n.v-application .title {\n  font-size: 1.16rem;\n  font-weight: 800;\n}\n\n.v-application .overline {\n  font-size: 0.625rem !important;\n  font-weight: 400;\n  letter-spacing: 0.1666666667em !important;\n  line-height: 1rem;\n  text-transform: uppercase;\n}\n\n.v-application .text-number {\n  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji !important;\n}\n\n.v-list-item__title {\n  font-size: 0.975rem;\n  font-weight: 600;\n}\n\n.v-list-item__action:first-child,\n.v-list-item__icon:first-child {\n  margin-right: 14px !important;\n}\n\n.v-application--is-rtl .v-list-item__action:first-child,\n.v-application--is-rtl .v-list-item__icon:first-child {\n  margin-right: 0 !important;\n  margin-left: 14px !important;\n}\n\n.v-list-item__action:first-child,\n.v-list-item__icon:first-child {\n  margin-right: 14px !important;\n}\n\n.v-list-group__header__append-icon .v-icon {\n  font-size: 1rem;\n}\n\n.v-list-group__header .v-list-item__icon.v-list-group__header__append-icon {\n  min-width: 0 !important;\n}\n\n.v-list-item__icon {\n  margin: auto;\n  justify-content: center;\n}\n\n.v-list-group--sub-group .v-list-group__header {\n  padding-left: 8px !important;\n}\n\n.v-navigation-drawer__content {\n  flex: 1 1 auto;\n}\n.v-navigation-drawer__content .v-list-item__prepend > .v-icon {\n  margin-inline-end: 14px;\n}\n\n.theme--dark .v-navigation-drawer__content {\n  background: none;\n}\n\n.v-table table {\n  padding: 4px;\n  padding-bottom: 8px;\n}\n.v-table table th {\n  text-transform: uppercase;\n  white-space: nowrap;\n}\n.v-table table td {\n  border-bottom: 0 !important;\n}\n.v-table table tbody tr {\n  transition: box-shadow 0.2s, transform 0.2s;\n}\n.v-table table tbody tr:not(.v-data-table__selected):hover {\n  box-shadow: 0 3px 15px -2px rgba(0, 0, 0, 0.12);\n  transform: translateY(-4px);\n}\n\n.v-tabs-items {\n  background-color: transparent !important;\n}\n\n.theme--dark.v-btn:not(.v-btn--flat):not(.v-btn--text):not(.v-btn--outlined) {\n  background-color: #273743;\n}\n\n.row {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n\n.v-app-bar .v-field--variant-solo {\n  box-shadow: none;\n}\n\n/*# sourceMappingURL=theme.css.map */\n"
  },
  {
    "path": "src/assets/scss/theme.scss",
    "content": "@use \"./vuetify/overrides\";\n\n@font-face {\n  font-family: 'Quicksand';\n  src: url(../fonts/Quicksand-VariableFont_wght.ttf);\n}\n\n.cursor-pointer {\n  cursor: pointer;\n}\n\n.cursor-move {\n  cursor: move;\n}\n\n.v-field-flat div {\n  box-shadow: none;\n}\n\n.top-z-index {\n  z-index: 1006 !important;\n}\n"
  },
  {
    "path": "src/assets/scss/vuetify/overrides.scss",
    "content": "@use \"./variables\" as *;\n@use \"vuetify/tools\" as *;\n\n.v-application {\n  .title {\n    font-size: map-deep-get($headings, 'h6', 'size');\n    font-weight: map-deep-get($headings, 'h6', 'weight');\n    line-height: map-deep-get($headings, 'h6', 'line-height');\n    letter-spacing: map-deep-get($headings, 'h6', 'letter-spacing');\n    font-family: map-deep-get($headings, 'h6', 'font-family');\n  }\n\n}\n\n.v-application .overline {\n  font-size: 0.625rem !important;\n  font-weight: 400;\n  letter-spacing: 0.1666666667em !important;\n  line-height: 1rem;\n  text-transform: uppercase;\n}\n\n.v-navigation-drawer__content {\n  .v-list-item__prepend > .v-icon {\n    margin-inline-end: 14px;\n  }\n}\n.v-card-title {\n  display: flex;\n}\n\n.v-data-table {\n  .v-table__wrapper > table {\n\n    & > thead,\n    & > tbody, {\n      & > tr {\n        & > th {\n          font-weight: bold;\n          color: rgba(var(--v-theme-on-surface));\n        }\n\n        transition: box-shadow 0.2s, transform 0.2s;\n\n        &:not(.v-data-table__selected):hover {\n          box-shadow: 0 3px 15px -2px rgba(0, 0, 0, 0.12);\n          transform: translateY(-4px);\n        }\n        font-size: $data-table-font-size;\n        & > td {\n          border-bottom: 0 !important;\n        }\n      }\n\n      padding: 4px 4px 8px;\n\n    }\n  }\n\n}\n.v-text-field-rounded {\n  & > .v-input__control {\n    border-radius: inherit;\n\n    & > .v-field {\n      border-radius: inherit;\n    }\n\n    & div {\n      &::before, &::after {\n        display: none;\n      }\n    }\n  }\n\n  border-radius: 28px;\n}\n"
  },
  {
    "path": "src/assets/scss/vuetify/variables/_elevations.scss",
    "content": "$shadow-key-umbra-opacity: rgba(85, 85, 85, 0.08) !default;\n$shadow-key-penumbra-opacity: rgba(85, 85, 85, 0.06) !default;\n$shadow-key-ambient-opacity: rgba(85, 85, 85, 0.03) !default;\n\n$shadow-key-umbra: (\n  0: (0px 0px 0px 0px $shadow-key-umbra-opacity),\n  1: (0px 2px 10px -1px $shadow-key-umbra-opacity),\n  2: (0px 3px 10px -2px $shadow-key-umbra-opacity),\n  3: (0px 3px 30px -2px $shadow-key-umbra-opacity),\n  4: (0px 2px 30px -1px $shadow-key-umbra-opacity),\n  5: (0px 3px 30px -1px $shadow-key-umbra-opacity),\n  6: (0px 3px 30px -1px $shadow-key-umbra-opacity),\n  7: (0px 4px 30px -2px $shadow-key-umbra-opacity),\n  8: (0px 5px 30px -3px $shadow-key-umbra-opacity),\n  9: (0px 5px 30px -3px $shadow-key-umbra-opacity),\n  10: (0px 6px 30px -3px $shadow-key-umbra-opacity),\n  11: (0px 6px 30px -4px $shadow-key-umbra-opacity),\n  12: (0px 7px 30px -4px $shadow-key-umbra-opacity),\n  13: (0px 7px 30px -4px $shadow-key-umbra-opacity),\n  14: (0px 7px 30px -4px $shadow-key-umbra-opacity),\n  15: (0px 8px 30px -5px $shadow-key-umbra-opacity),\n  16: (0px 8px 30px -5px $shadow-key-umbra-opacity),\n  17: (0px 8px 30px -5px $shadow-key-umbra-opacity),\n  18: (0px 9px 30px -5px $shadow-key-umbra-opacity),\n  19: (0px 9px 30px -6px $shadow-key-umbra-opacity),\n  20: (0px 10px 30px -6px $shadow-key-umbra-opacity),\n  21: (0px 10px 30px -6px $shadow-key-umbra-opacity),\n  22: (0px 10px 30px -6px $shadow-key-umbra-opacity),\n  23: (0px 11px 30px -7px $shadow-key-umbra-opacity),\n  24: (0px 11px 30px -7px $shadow-key-umbra-opacity)\n);\n\n$shadow-key-penumbra: (\n  0: (0px 0px 0px 0px $shadow-key-penumbra-opacity),\n  1: (0px 1px 10px 0px $shadow-key-penumbra-opacity),\n  2: (0px 2px 20px 0px $shadow-key-penumbra-opacity),\n  3: (0px 3px 40px 0px $shadow-key-penumbra-opacity),\n  4: (0px 4px 30px 0px $shadow-key-penumbra-opacity),\n  5: (0px 5px 30px 0px $shadow-key-penumbra-opacity),\n  6: (0px 6px 30px 0px $shadow-key-penumbra-opacity),\n  7: (0px 7px 30px 1px $shadow-key-penumbra-opacity),\n  8: (0px 8px 30px 1px $shadow-key-penumbra-opacity),\n  9: (0px 9px 30px 1px $shadow-key-penumbra-opacity),\n  10: (0px 10px 30px 1px $shadow-key-penumbra-opacity),\n  11: (0px 11px 30px 1px $shadow-key-penumbra-opacity),\n  12: (0px 12px 30px 2px $shadow-key-penumbra-opacity),\n  13: (0px 13px 30px 2px $shadow-key-penumbra-opacity),\n  14: (0px 14px 30px 2px $shadow-key-penumbra-opacity),\n  15: (0px 15px 30px 2px $shadow-key-penumbra-opacity),\n  16: (0px 16px 30px 2px $shadow-key-penumbra-opacity),\n  17: (0px 17px 30px 2px $shadow-key-penumbra-opacity),\n  18: (0px 18px 30px 2px $shadow-key-penumbra-opacity),\n  19: (0px 19px 30px 2px $shadow-key-penumbra-opacity),\n  20: (0px 20px 30px 3px $shadow-key-penumbra-opacity),\n  21: (0px 21px 30px 3px $shadow-key-penumbra-opacity),\n  22: (0px 22px 30px 3px $shadow-key-penumbra-opacity),\n  23: (0px 23px 30px 3px $shadow-key-penumbra-opacity),\n  24: (0px 24px 30px 3px $shadow-key-penumbra-opacity)\n);\n\n$shadow-key-ambient: (\n  0: (0px 0px 0px 0px $shadow-key-ambient-opacity),\n  1: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  2: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  3: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  4: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  5: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  6: (0px 1px 30px 0px $shadow-key-ambient-opacity),\n  7: (0px 2px 30px 1px $shadow-key-ambient-opacity),\n  8: (0px 3px 30px 2px $shadow-key-ambient-opacity),\n  9: (0px 3px 30px 2px $shadow-key-ambient-opacity),\n  10: (0px 4px 30px 3px $shadow-key-ambient-opacity),\n  11: (0px 4px 30px 3px $shadow-key-ambient-opacity),\n  12: (0px 5px 30px 4px $shadow-key-ambient-opacity),\n  13: (0px 5px 30px 4px $shadow-key-ambient-opacity),\n  14: (0px 5px 30px 4px $shadow-key-ambient-opacity),\n  15: (0px 6px 30px 5px $shadow-key-ambient-opacity),\n  16: (0px 6px 30px 5px $shadow-key-ambient-opacity),\n  17: (0px 6px 30px 5px $shadow-key-ambient-opacity),\n  18: (0px 7px 30px 6px $shadow-key-ambient-opacity),\n  19: (0px 7px 30px 6px $shadow-key-ambient-opacity),\n  20: (0px 8px 30px 7px $shadow-key-ambient-opacity),\n  21: (0px 8px 40px 7px $shadow-key-ambient-opacity),\n  22: (0px 8px 40px 7px $shadow-key-ambient-opacity),\n  23: (0px 9px 40px 8px $shadow-key-ambient-opacity),\n  24: (0px 9px 40px 8px $shadow-key-ambient-opacity)\n);\n"
  },
  {
    "path": "src/assets/scss/vuetify/variables/_font.scss",
    "content": "$body-font-family: 'Quicksand', sans-serif;\n$font-size-root: 15px;\n$line-height-root: 1.5;\n\n$font-weights: (\n        'thin': 300,\n        'light': 300,\n        'regular': 400,\n        'medium': 500,\n        'bold': 600,\n        'black': 700\n);\n\n$heading-font-family: $body-font-family;\n\n$headings: (\n        'h6': (\n                'size': 1.16rem,\n                'weight': 800,\n                'line-height': 2rem,\n                'letter-spacing': .0125em,\n                'font-family': $heading-font-family\n        ),\n        'subtitle-1': (\n                'size': 1rem,\n                'weight': 600,\n                'line-height': 1.75rem,\n                'letter-spacing': .009375em,\n                'font-family': $body-font-family\n        ),\n        'caption': (\n                'size': .75rem,\n                'weight': 400,\n                'letter-spacing': .0333333333em,\n                'line-height': 1.25rem,\n                'font-family': $body-font-family\n        ),\n        'overline': (\n                'size': .625rem,\n                'weight': 400,\n                'letter-spacing': .1666666667em,\n                'line-height': 1rem,\n                'font-family': $body-font-family\n        )\n);\n"
  },
  {
    "path": "src/assets/scss/vuetify/variables/_global.scss",
    "content": "// Global border radius\n$border-radius-root: 6px;\n\n// Spacing\n$spacer: 8px;\n\n$grid-breakpoints: (\n        'xs': 0,\n        'sm': 600px,\n        'md': 960px,\n        'lg': 1280px - 16px,\n        'xl': 1920px - 16px\n);\n\n$display-breakpoints: (\n        'print-only': 'only print',\n        'screen-only': 'only screen',\n        'xs-only': 'only screen and (max-width: #{map-get($grid-breakpoints, 'sm') - 1})',\n        'sm-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'sm')}) and (max-width: #{map-get($grid-breakpoints, 'md') - 1})',\n        'sm-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'md') - 1})',\n        'sm-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'sm')})',\n        'md-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'md')}) and (max-width: #{map-get($grid-breakpoints, 'lg') - 1})',\n        'md-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'lg') - 1})',\n        'md-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'md')})',\n        'lg-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'lg')}) and (max-width: #{map-get($grid-breakpoints, 'xl') - 1})',\n        'lg-and-down': 'only screen and (max-width: #{map-get($grid-breakpoints, 'xl') - 1})',\n        'lg-and-up': 'only screen and (min-width: #{map-get($grid-breakpoints, 'lg')})',\n        'xl-only': 'only screen and (min-width: #{map-get($grid-breakpoints, 'xl')})'\n);\n\n// Transitions\n$transition: (\n        'fast-out-slow-in': cubic-bezier(0.4, 0, 0.2, 1),\n        'linear-out-slow-in': cubic-bezier(0, 0, 0.2, 1),\n        'fast-out-linear-in': cubic-bezier(0.4, 0, 1, 1),\n        'ease-in-out': cubic-bezier(0.4, 0, 0.6, 1),\n        'fast-in-fast-out': cubic-bezier(0.25, 0.8, 0.25, 1),\n        'swing': cubic-bezier(0.25, 0.8, 0.5, 1)\n);\n\n// Inputs and labels\n$label-font-size: 14px;\n$input-font-size: 14px;\n$input-top-spacing: 16px;\n$text-field-active-label-height: 12px;\n\n// Button\n$btn-font-weight: 700;\n$btn-text-transform: none;\n$btn-letter-spacing: normal;\n\n// List\n$list-item-dense-title-font-size: 0.875rem;\n$list-item-dense-title-font-weight: 600;\n\n// Table\n$data-table-expanded-content-box-shadow: inset 0px 4px 10px -5px rgba(50, 50, 50, 0.1), inset 0px -4px 6px -5px rgba(50, 50, 50, 0.1);\n$data-table-font-size: 0.875em;\n// Tabs\n$tab-font-weight: 700;\n\n// Toolbar\n$toolbar-transition:\n        0.2s map-get($transition, 'fast-out-slow-in') transform,\n        0.2s map-get($transition, 'fast-out-slow-in') left,\n        0.2s map-get($transition, 'fast-out-slow-in') right,\n        280ms map-get($transition, 'fast-out-slow-in') box-shadow,\n        0.25s map-get($transition, 'fast-out-slow-in') max-width,\n        0.25s map-get($transition, 'fast-out-slow-in') width;\n"
  },
  {
    "path": "src/assets/scss/vuetify/variables/_index.scss",
    "content": "@forward './_global';\n@forward './_font';\n@forward './_elevations';\n"
  },
  {
    "path": "src/components/common/Breadcrumb.vue",
    "content": "<template>\n  <v-breadcrumbs class=\"pa-0 py-2\" :items=\"breadcrumbs\" active-color=\"primary\">\n    <template v-slot:title=\"{ item}\">\n      {{ $t((item as App.GlobalBreadcrumb ).title || 'unknown') }}\n    </template>\n  </v-breadcrumbs>\n</template>\n\n<script setup lang=\"ts\">\nimport {PropType} from 'vue';\nimport useBreadcrumb from \"@/hooks/common/useBreadcrumb\";\n\nconst props = defineProps({\n  root: {\n    type: String as PropType<Exclude<AuthRoute.AllRouteKey, 'not-found'>>,\n    default: () => 'root'\n  }\n})\n\nconst {breadcrumbs} = useBreadcrumb(props.root)\n\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "src/components/common/CopyLabel.vue",
    "content": "<template>\n  <div ref=\"copyLabel\" class=\"copylabel animate__faster\" @click.stop.prevent=\"copy\">{{ text }}\n    <v-tooltip location=\"bottom\" activator=\"parent\">\n      <span>{{ tooltip }}</span>\n    </v-tooltip>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst props = defineProps({\n  // Text to copy to clipboard.ts\n  text: {\n    type: String,\n    default: ''\n  },\n  // Text to show on toast\n  toastText: {\n    type: String,\n    default: 'Copied to clipboard.ts!'\n  },\n  animation: {\n    type: String,\n    default: 'heartBeat'\n  }\n})\nconst tooltip = ref('copy')\nconst copyLabel = ref<HTMLElement>()\nlet timeout: NodeJS.Timeout\nonBeforeUnmount(() => {\n  if (timeout) clearTimeout(timeout)\n})\nconst copy = () => {\n  if (copyLabel.value) {\n    animate(copyLabel.value, props.animation)\n  }\n  clipboard(props.text, props.toastText)\n  tooltip.value = 'Copied!'\n  timeout = setTimeout(() => {\n    tooltip.value = 'Copy'\n  }, 2000)\n}\n</script>\n\n<style scoped>\n.copylabel {\n  cursor: pointer;\n  display: inline-block;\n  border-bottom: 1px dashed;\n}\n</style>\n"
  },
  {
    "path": "src/components/common/FlagIcon.vue",
    "content": "<template>\n  <span class=\"fi\" :class=\"[`fi-${flag}`, { 'flag-round': round }]\"></span>\n</template>\n\n<script setup lang=\"ts\">\nimport \"flag-icons/css/flag-icons.min.css\"\n\ndefineProps(\n  {\n    flag: {\n      type: String,\n      default: 'us'\n    },\n    round: {\n      type: Boolean,\n      default: false\n    }\n  }\n)\n\n</script>\n\n<style lang=\"scss\" scoped>\n.fi {\n  height: 22px;\n  width: 22px;\n\n  &.flag-round {\n    background-size: cover;\n    border-radius: 100%;\n    height: 26px;\n    width: 26px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/common/SideConfigMenu.vue",
    "content": "<template>\n  <div>\n    <v-btn\n      theme=\"dark\"\n      ref=\"refButton\"\n      class=\"drawer-button\"\n      color=\"#ee44aa\"\n      @click=\"right = true\"\n    >\n      <v-icon class=\"fa-spin\">mdi-cog-outline</v-icon>\n    </v-btn>\n\n    <v-navigation-drawer\n      v-model=\"right\"\n      location=\"right\"\n      floating\n      temporary\n      order=\"-10\"\n      width=\"310\"\n    >\n      <div class=\"d-flex align-center pa-2\">\n        <div class=\"title\">Settings</div>\n        <v-spacer></v-spacer>\n        <v-btn flat icon @click=\"right = false\">\n          <v-icon>mdi-close</v-icon>\n        </v-btn>\n      </div>\n\n      <v-divider></v-divider>\n\n      <div class=\"pa-2\">\n        <div class=\"font-weight-bold my-1\">Follow Os Theme</div>\n        <v-switch v-model=\"themeConfig.followOs\" color=\"primary\"></v-switch>\n        <div class=\"font-weight-bold my-1\">Global Theme</div>\n        <v-btn-toggle v-model=\"themeConfig.globalTheme\" color=\"primary\" mandatory variant=\"outlined\" class=\"mb-2\">\n          <v-btn value=\"light\">Light</v-btn>\n          <v-btn value=\"dark\">Dark</v-btn>\n        </v-btn-toggle>\n\n        <div class=\"font-weight-bold my-1\">Toolbar Theme</div>\n        <v-btn-toggle v-model=\"themeConfig.toolbarTheme\" color=\"primary\" mandatory variant=\"outlined\" class=\"mb-2\">\n          <v-btn value=\"global\">Global</v-btn>\n          <v-btn value=\"light\">Light</v-btn>\n          <v-btn value=\"dark\">Dark</v-btn>\n        </v-btn-toggle>\n\n        <div class=\"font-weight-bold my-1\">Toolbar Style</div>\n        <v-btn-toggle v-model=\"themeConfig.isToolbarDetached\" color=\"primary\" mandatory variant=\"outlined\" class=\"mb-2\">\n          <v-btn :value=\"false\">Full</v-btn>\n          <v-btn :value=\"true\">Solo</v-btn>\n        </v-btn-toggle>\n\n        <div class=\"font-weight-bold my-1\">Content Layout</div>\n        <v-btn-toggle v-model=\"themeConfig.isContentBoxed\" color=\"primary\" mandatory variant=\"outlined\" class=\"mb-2\">\n          <v-btn :value=\"false\">Fluid</v-btn>\n          <v-btn :value=\"true\">Boxed</v-btn>\n        </v-btn-toggle>\n\n        <div class=\"font-weight-bold my-1\">Menu Theme</div>\n        <v-btn-toggle v-model=\"themeConfig.menuTheme\" color=\"primary\" mandatory variant=\"outlined\" class=\"mb-2\">\n          <v-btn value=\"global\">Global</v-btn>\n          <v-btn value=\"light\">Light</v-btn>\n          <v-btn value=\"dark\">Dark</v-btn>\n        </v-btn-toggle>\n\n        <div class=\"font-weight-bold my-1\">Primary Color</div>\n\n        <v-color-picker v-model=\"themeConfig.primary\" mode=\"hexa\" :swatches=\"swatches\" show-swatches></v-color-picker>\n      </div>\n\n      <v-divider></v-divider>\n    </v-navigation-drawer>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {ComponentPublicInstance} from \"vue\";\nimport {useThemeStore} from \"@/store\";\n\nconst themeConfig = useThemeStore()\nconst right = ref(false)\nlet timeout: NodeJS.Timeout\nconst swatches = reactive([['#0096c7', '#31944f'],\n  ['#EE4f12', '#46BBB1'],\n  ['#ee44aa', '#55BB46']])\nconst refButton = ref<ComponentPublicInstance>()\nconst execAnimate = () => {\n  if (timeout) {\n    clearTimeout(timeout)\n  }\n  const time = (Math.floor(Math.random() * 10 + 1) + 10) * 1000\n  timeout = setTimeout(() => {\n    if (refButton.value) {\n      animate(refButton.value.$el, 'bounce')\n    }\n    execAnimate()\n  }, time)\n}\n\nonMounted(() => {\n  execAnimate()\n})\nonBeforeUnmount(() => {\n  if (timeout) clearTimeout(timeout)\n})\n\n</script>\n\n<style lang=\"scss\" scoped>\n.drawer-button {\n  position: fixed;\n  top: 340px;\n  right: -20px;\n  z-index: 9999;\n  box-shadow: 1px 1px 18px #ee44aa;\n\n  .v-icon {\n    margin-left: -18px;\n    font-size: 1.3rem;\n  }\n\n  .fa-spin {\n    animation: spin 2s infinite linear;\n  }\n\n  @keyframes spin {\n    from {\n      transform: rotate(0deg);\n    }\n    to {\n      transform: rotate(360deg);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/common/SvgIcon.vue",
    "content": "<template>\n  <svg aria-hidden=\"true\" v-bind=\"bindAttrs\" class=\"d-flex flex-column justify-end\" >\n    <use :href=\"symbolId\" fill=\"currentColor\"/>\n  </svg>\n</template>\n\n<script lang=\"ts\" setup>\nconst props = defineProps({\n  name: {\n    type: String,\n    required: true,\n  },\n})\nconst attrs = useAttrs();\nconst bindAttrs = computed<{ class: string; style: string }>(() => ({\n  class: (attrs.class as string) || '',\n  style: (attrs.style as string) || ''\n}));\n\nconst symbolId = computed(() => {\n  const {VITE_ICON_PREFFIX: prefix} = import.meta.env;\n\n  return `#${prefix}-${props.name}`\n})\n</script>\n"
  },
  {
    "path": "src/components/common/TrendPercent.vue",
    "content": "<template>\n  <span>\n    <span v-if=\"value === 0\">\n      {{ value }}%\n    </span>\n    <span v-else-if=\"value > 0\" class=\"success--text\">\n      <v-icon small color=\"success\">mdi-arrow-top-right</v-icon> {{ value }}%\n    </span>\n    <span v-else class=\"error--text\">\n      <v-icon small color=\"error\">mdi-arrow-bottom-right</v-icon> {{ Math.abs(value) }}%\n    </span>\n  </span>\n</template>\n\n<script setup lang=\"ts\">\ndefineProps({\n  value: {\n    type: Number,\n    default: 0\n  }\n})\n</script>\n"
  },
  {
    "path": "src/components/dashboard/ActivityCard.vue",
    "content": "<template>\n  <v-card>\n    <v-card-title>\n      <div>{{ $t('dashboard.activity') }}</div>\n      <v-spacer></v-spacer>\n      <v-menu offset-y left transition=\"slide-y-transition\">\n        <template v-slot:activator=\"{ props }\">\n          <v-btn icon v-bind=\"props\" flat>\n            <v-icon>mdi-dots-vertical</v-icon>\n          </v-btn>\n        </template>\n        <v-list density=\"comfortable\">\n          <v-list-item\n            v-for=\"(item,index) in menu\"\n            :key=\"index\"\n            :to=\"item.link\"\n            :disabled=\"item.disabled\"\n            link\n            :prepend-icon=\"item.icon\"\n            :title=\"item.text\"\n          >\n          </v-list-item>\n        </v-list>\n      </v-menu>\n    </v-card-title>\n\n    <v-card-text>\n      <v-timeline class=\"pa-0\" density=\"comfortable\" align=\"start\" truncate-line=\"start\">\n        <v-timeline-item v-for=\"(item,index) in activity\" :key=\"index\" :dot-color=\"item.color\" size=\"small\">\n          <strong>{{ item.what }}</strong>\n          <div class=\"text-caption\">\n            <div>{{ item.where }}</div>\n            <div class=\"text-grey\">{{ item.when }}</div>\n          </div>\n        </v-timeline-item>\n      </v-timeline>\n    </v-card-text>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport {reactive} from \"vue\";\n\ninterface _menu {\n  icon: string,\n  text: string,\n  disabled?: boolean,\n  link?: string\n}\n\nconst menu = reactive<Array<_menu>>([\n  {icon: 'mdi-refresh', text: 'Refresh'},\n  {icon: 'mdi-delete-outline', text: 'Clear'}\n])\nconst activity = reactive([\n  {\n    what: 'New Emoji',\n    where: 'Chat App',\n    when: '4pm',\n    color: 'primary'\n  }, {\n    what: 'Design Stand Up',\n    where: 'Chat App',\n    when: '2pm',\n    color: 'purple'\n  }, {\n    what: 'Lunch Break',\n    where: '',\n    when: '11am',\n    color: 'primary'\n  }, {\n    what: 'Answer Emails',\n    where: 'Work work',\n    when: '9pm',\n    color: 'teal lighten-3'\n  }\n])\n\n</script>\n"
  },
  {
    "path": "src/components/dashboard/SalesCard.vue",
    "content": "<template>\n  <v-card class=\"d-flex flex-grow-1 bg-primary-darken-4 \" theme=\"dark\">\n\n    <!-- loading spinner -->\n    <div v-if=\"loading\" class=\"d-flex flex-grow-1 align-center justify-center\">\n      <v-progress-circular indeterminate color=\"primary\"></v-progress-circular>\n    </div>\n\n    <!-- information -->\n    <div v-else class=\"d-flex flex-column flex-grow-1\">\n      <v-card-title class=\"d-flex\">\n        <div>{{ $t(label) }}</div>\n        <v-spacer></v-spacer>\n        <v-btn variant=\"text\" color=\"primary\" @click=\"$emit('action-clicked')\">{{ actionLabel }}</v-btn>\n      </v-card-title>\n\n      <div class=\"d-flex flex-column flex-grow-1\">\n        <div class=\"pa-2\">\n          <div class=\"text-h4\">\n            {{ $filters.formatCurrency(26358.49) }}\n          </div>\n          <div class=\"text-primary-lighten-1 mt-1\">\n            {{ $filters.formatCurrency(7123.21) }} {{ $t('dashboard.lastweek') }}\n          </div>\n        </div>\n\n        <v-spacer></v-spacer>\n\n        <div class=\"px-2 pb-2\">\n          <div class=\"title mb-1\">{{ $t('dashboard.weeklySales') }}</div>\n          <div class=\"d-flex align-center\">\n            <div class=\"text-h4\">\n              {{ $filters.formatCurrency(value) }}\n            </div>\n            <v-spacer></v-spacer>\n            <div class=\"d-flex flex-column text-right\">\n              <div class=\"font-weight-bold\">\n                <trend-percent :value=\"percentage\"/>\n              </div>\n              <div class=\"text-caption\">{{ percentageLabel }}</div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n\n      <apexchart\n        type=\"area\"\n        height=\"120\"\n        :options=\"chartOptions\"\n        :series=\"series\"\n      ></apexchart>\n    </div>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport moment from 'moment';\n\nconst formatDate = (date: string) => {\n  return date ? moment(date).format('D MMM') : ''\n}\n\nconst props = defineProps({\n  value: {\n    type: Number,\n    default: 0\n  },\n  percentage: {\n    type: Number,\n    default: 0\n  },\n  percentageLabel: {\n    type: String,\n    default: 'vs. last week'\n  },\n  series: {\n    type: Array,\n    default: () => [{\n      name: 'Sales',\n      data: [11, 32, 45, 13]\n    }]\n  },\n  xaxis: {\n    type: Object,\n    default: () => ({\n      type: 'category',\n      categories: [\n        '2018-09-19T00:00:00.000Z',\n        '2018-09-20T00:00:00.000Z',\n        '2018-09-22T00:00:00.000Z',\n        '2018-09-23T00:00:00.000Z'\n      ]\n      // tickAmount: 3\n    })\n  },\n  label: {\n    type: String,\n    default: 'dashboard.sales'\n  },\n  actionLabel: {\n    type: String,\n    default: 'View Report'\n  },\n  options: {\n    type: Object,\n    default: () => ({})\n  },\n  loading: {\n    type: Boolean,\n    default: false\n  }\n})\n\nconst {themes, current} = useTheme()\n\nconst chartOptions = computed(() => {\n  const primaryColor = current.value.dark\n    ? themes.value[\"dark\"].colors.primary\n    : themes.value[\"light\"].colors.primary\n\n  return {\n    chart: {\n      height: 120,\n      type: 'area',\n      sparkline: {\n        enabled: true\n      },\n      animations: {\n        speed: 400\n      }\n    },\n    series: props.series,\n    colors: [primaryColor],\n    fill: {\n      type: 'solid',\n      colors: [primaryColor],\n      opacity: 0.15\n    },\n    stroke: {\n      curve: 'smooth',\n      width: 2\n    },\n    xaxis: props.xaxis,\n    tooltip: {\n      followCursor: true,\n      theme: 'dark',\n      custom: function ({ctx, series, seriesIndex, dataPointIndex, w}: any) {\n        const seriesName = w.config.series[seriesIndex].name\n\n        return `<div class=\"rounded-lg pa-1 text-caption\">\n              <div class=\"font-weight-bold\">${formatDate(w.globals.categoryLabels[dataPointIndex])}</div>\n              <div>${series[seriesIndex][dataPointIndex]} ${seriesName}</div>\n            </div>`\n      }\n    },\n    ...props.options\n  }\n})\n</script>\n"
  },
  {
    "path": "src/components/dashboard/SourcesCard.vue",
    "content": "<template>\n  <v-card class=\"d-flex flex-column flex-grow-1\">\n\n    <!-- loading spinner -->\n    <div v-if=\"loading\" class=\"d-flex flex-grow-1 align-center justify-center\">\n      <v-progress-circular indeterminate color=\"primary\"></v-progress-circular>\n    </div>\n\n    <!-- information -->\n    <div v-else class=\"d-flex flex-column flex-grow-1\">\n      <v-card-title>\n        <div>{{ label }}</div>\n        <v-spacer></v-spacer>\n        <div>\n          <v-select\n            v-model=\"selectedInterval\"\n            density=\"comfortable\"\n            variant=\"solo\"\n            hide-details\n            hide-selected\n            :items=\"intervals\"\n          ></v-select>\n        </div>\n      </v-card-title>\n\n      <div class=\"chart-wrap\">\n        <apexchart\n          type=\"donut\"\n          width=\"85%\"\n          :options=\"chartOptions\"\n          :series=\"series\"\n        ></apexchart>\n      </div>\n    </div>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport {computed, reactive, ref} from \"vue\";\n\nconst props = defineProps({\n  series: {\n    type: Array,\n    default: () => ([])\n  },\n  label: {\n    type: String,\n    default: ''\n  },\n  color: {\n    type: String,\n    default: '#333333'\n  },\n  value: {\n    type: Number,\n    default: 0\n  },\n  percentage: {\n    type: Number,\n    default: 0\n  },\n  percentageLabel: {\n    type: String,\n    default: 'vs. last week'\n  },\n  options: {\n    type: Object,\n    default: () => ({})\n  },\n  loading: {\n    type: Boolean,\n    default: false\n  }\n})\n\nconst selectedInterval = ref('Last 7 days')\n\nconst intervals = reactive([\n  'Last 7 days',\n  'Last 28 days',\n  'Last month',\n  'Last year'\n])\n\nconst {current} = useTheme()\n\nconst chartOptions = computed(() => {\n  return {\n    chart: {\n      type: 'donut',\n      animations: {\n        speed: 400\n      },\n      background: 'transparent'\n    },\n    stroke: {\n      show: true,\n      colors: [current.value.dark ? '#333' : '#fff'],\n      width: 1,\n      dashArray: 0\n    },\n    plotOptions: {\n      pie: {\n        expandOnClick: false,\n        donut: {\n          size: '74%'\n        }\n      }\n    },\n    theme: {\n      mode: current.value.dark ? 'dark' : 'light'\n    },\n    labels: ['Referrals', 'Organic Search', 'Social Media', 'Others'],\n    dataLabels: {\n      enabled: false\n    },\n    colors: ['#2364aa', '#3da5d9', '#73bfb8', '#fec601', '#ea7317'],\n    fill: {\n      colors: ['#2364aa', '#3da5d9', '#73bfb8', '#fec601', '#ea7317']\n    },\n    legend: {\n      offsetY: 40,\n      fontSize: '13px',\n      fontFamily: 'Quicksand',\n      fontWeight: 700\n    },\n    responsive: [{\n      breakpoint: 480,\n      options: {\n        chart: {\n          width: 200\n        },\n        legend: {\n          offsetY: 0,\n          position: 'bottom'\n        }\n      }\n    }],\n    ...props.options\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.chart-wrap {\n  max-width: 560px;\n  max-height: 280px;\n}\n</style>\n"
  },
  {
    "path": "src/components/dashboard/TableCard.vue",
    "content": "<template>\n  <v-card>\n    <v-card-title>{{ label }}</v-card-title>\n    <v-data-table\n      :headers=\"headers\"\n      :items=\"items\"\n      hide-default-footer\n    >\n      <!--      <template v-slot:bottom>-->\n      <!--      </template>-->\n      <template v-slot:item.id=\"{ item:{raw} }\">\n        <div class=\"font-weight-bold\">#\n          <copy-label :text=\"raw.id\"/>\n        </div>\n      </template>\n\n      <template v-slot:item.user=\"{ item:{raw}}\">\n        <div class=\"d-flex align-center py-1\">\n          <v-avatar size=\"40\" class=\"elevation-1 grey lighten-3\">\n            <svg-icon :name=\"raw.user.avatar\"></svg-icon>\n          </v-avatar>\n          <div class=\"ml-1\">\n            <div class=\"font-weight-bold\">{{ raw.user.name }}</div>\n            <div class=\"text-caption\">\n              <copy-label :text=\"raw.user.email\"/>\n            </div>\n          </div>\n        </div>\n      </template>\n\n      <template v-slot:item.date=\"{ item:{raw} }\">\n        <div>{{ $filters.formatCurrency(raw.date) }}</div>\n      </template>\n\n      <template v-slot:item.company=\"{ item:{raw} }\">\n        <copy-label :text=\"raw.company\"/>\n      </template>\n\n      <template v-slot:item.amount=\"{ item:{raw} }\">\n        {{ $filters.formatCurrency(raw.amount) }}\n      </template>\n\n      <template v-slot:item.status=\"{ item:{raw} }\">\n        <div class=\"font-weight-bold d-flex align-center text-no-wrap\">\n          <div v-if=\"raw.status === 'PENDING'\" class=\"warning--text\">\n            <v-icon small color=\"warning\">mdi-circle-medium</v-icon>\n            <span>Pending</span>\n          </div>\n          <div v-if=\"raw.status === 'PAID'\" class=\"success--text\">\n            <v-icon small color=\"success\">mdi-circle-medium</v-icon>\n            <span>Paid</span>\n          </div>\n        </div>\n      </template>\n\n      <template v-slot:item.action>\n        <div class=\"actions\">\n          <v-btn flat icon >\n            <v-icon>mdi-open-in-new</v-icon>\n          </v-btn>\n        </div>\n      </template>\n    </v-data-table>\n  </v-card>\n</template>\n\n<script setup lang=\"ts\">\nimport {ref} from 'vue'\ndefineProps\n({\n  label: {\n    type: String,\n    default: ''\n  }\n})\nconst headers = ref<DataTableHeader>([\n  {title: 'Order Id', align: 'start', key: 'id'},\n  {\n    title: 'User',\n    sortable: false,\n    key: 'user'\n  },\n  {title: 'Date', key: 'date'},\n  {title: 'Company', key: 'company'},\n  {title: 'Amount', key: 'amount'},\n  {title: 'Status', key: 'status'},\n  {title: '', sortable: false, align: 'end', key: 'action'}\n])\n\n\n\nconst items = ref([\n  {\n    id: '2837',\n    user: {\n      name: 'John Simon',\n      email: 'johnsimon@blobhill.com',\n      avatar: 'avatar1',\n    },\n    date: '2020-05-10',\n    company: 'BlobHill',\n    amount: 52877,\n    status: 'PAID'\n  },\n  {\n    id: '2838',\n    user: {\n      name: 'Greg Cool J',\n      email: 'cool@caprimooner.com',\n      avatar: 'avatar2',\n    },\n    date: '2020-05-11',\n    company: 'Caprimooner',\n    amount: 2123,\n    status: 'PENDING'\n  },\n  {\n    id: '2839',\n    user: {\n      name: 'Samantha Bush',\n      email: 'bush@catloveisstilllove.com',\n      avatar: 'avatar3',\n    },\n    date: '2020-05-11',\n    company: 'CatLovers',\n    amount: 12313,\n    status: 'PENDING'\n  },\n  {\n    id: '2840',\n    user: {\n      name: 'Ben Howard',\n      email: 'ben@indiecoolers.com',\n      avatar: 'avatar4',\n    },\n    date: '2020-05-12',\n    company: 'IndieCoolers',\n    amount: 9873,\n    status: 'PAID'\n  },\n  {\n    id: '2841',\n    user: {\n      name: 'Jack Candy',\n      email: 'jack@candylooove.com',\n      avatar: 'avatar5',\n    },\n    date: '2020-05-13',\n    company: 'CandyLooove',\n    amount: 29573,\n    status: 'PAID'\n  }\n])\n</script>\n"
  },
  {
    "path": "src/components/dashboard/TodoCard.vue",
    "content": "<template>\n  <tasks-page class=\"todo-card\"></tasks-page>\n</template>\n\n<script lang=\"ts\" setup>\nimport TasksPage from '@/views/todo/pages/TasksPage.vue'\n</script>\n<style lang=\"scss\" scoped>\n.todo-card {\n  height: 100%;\n  max-height: 360px;\n  overflow-y: scroll;\n}\n</style>\n"
  },
  {
    "path": "src/components/dashboard/TrackCard.vue",
    "content": "<template>\n  <v-card class=\"d-flex flex-column flex-grow-1\">\n    <div v-if=\"loading\" class=\"d-flex flex-grow-1 align-center justify-center\">\n      <v-progress-circular indeterminate color=\"primary\"></v-progress-circular>\n    </div>\n    <div v-else class=\"d-flex flex-column flex-grow-1\">\n      <v-card-title>\n        {{ label }}\n      </v-card-title>\n\n      <div class=\"d-flex flex-column flex-grow-1\">\n        <div class=\"px-2 pb-2\">\n          <div class=\"d-flex align-center\">\n            <div class=\"text-h4\">{{ value }}</div>\n            <v-spacer></v-spacer>\n            <div class=\"d-flex flex-column text-right\">\n              <div class=\"font-weight-bold\">\n                <trend-percent :value=\"percentage\"/>\n              </div>\n              <div class=\"text-caption\">{{ percentageLabel }}</div>\n            </div>\n          </div>\n        </div>\n\n        <v-spacer></v-spacer>\n\n        <apexchart\n          type=\"area\"\n          height=\"60\"\n          :options=\"chartOptions\"\n          :series=\"series\"\n        ></apexchart>\n      </div>\n    </div>\n  </v-card>\n</template>\n\n<script setup lang=\"ts\">\nimport moment from 'moment'\n\nconst formatDate = (date: string) => {\n  return date ? moment(date).format('D MMM') : ''\n}\n\nconst props = defineProps({\n  series: {\n    type: Array,\n    default: () => ([])\n  },\n  label: {\n    type: String,\n    default: ''\n  },\n  color: {\n    type: String,\n    default: '#333333'\n  },\n  value: {\n    type: Number,\n    default: 0\n  },\n  percentage: {\n    type: Number,\n    default: 0\n  },\n  percentageLabel: {\n    type: String,\n    default: 'vs. last week'\n  },\n  options: {\n    type: Object,\n    default: () => ({})\n  },\n  loading: {\n    type: Boolean,\n    default: false\n  }\n})\nconst chartOptions = computed(() => {\n\n  return {\n    chart: {\n      animations: {\n        speed: 400,\n        animateGradually: {\n          enabled: false\n        }\n      },\n      width: '100%',\n      height: 60,\n      type: 'area',\n      sparkline: {\n        enabled: true\n      }\n    },\n    colors: [props.color],\n    fill: {\n      type: 'solid',\n      colors: [props.color],\n      opacity: 0.15\n    },\n    stroke: {\n      curve: 'smooth',\n      width: 2\n    },\n    xaxis: {\n      type: 'datetime'\n    },\n    tooltip: {\n      followCursor: true,\n      theme: 'dark', //this.$vuetify.theme.isDark ? 'light' : 'dark',\n      custom: function ({ctx, series, seriesIndex, dataPointIndex, w}: any) {\n        const seriesName = w.config.series[seriesIndex].name\n        const dataPoint = w.config.series[seriesIndex].data[dataPointIndex]\n\n        return `<div class=\"rounded-lg pa-1 text-caption\">\n              <div class=\"font-weight-bold\">${formatDate(dataPoint[0])}</div>\n              <div>${dataPoint[1]} ${seriesName}</div>\n            </div>`\n      }\n    },\n    ...props.options\n  }\n})\n</script>\n"
  },
  {
    "path": "src/components/navigation/MainMenu.vue",
    "content": "<template>\n  <v-list nav>\n    <div v-for=\"(item,index) in menu\" :key=\"index\">\n      <div v-if=\"item.label\" class=\"pa-1 mt-2 overline\">{{ $t(item.label) }}</div>\n      <nav-menu :menu=\"item.children\"/>\n    </div>\n  </v-list>\n</template>\n\n<script lang=\"ts\" setup>\nimport {PropType} from \"vue\";\n\ndefineProps({\n  menu: {\n    type: Array as PropType<Array<App.GlobalMenuOption>>,\n    default: () => []\n  }\n})\n</script>\n"
  },
  {
    "path": "src/components/navigation/NavMenu.vue",
    "content": "<template>\n  <div>\n    <nav-menu-item v-for=\"(level1Item, level1Index) in menu\" :key=\"level1Index\" :menu-item=\"level1Item\">\n      <template v-if=\"level1Item.children\">\n\n        <nav-menu-item\n          v-for=\"(level2Item, level2Index) in level1Item.children\"\n          :key=\"level2Index\"\n          :menu-item=\"level2Item\"\n          subgroup\n          small\n        >\n          <template v-if=\"level2Item.children\">\n\n            <nav-menu-item\n              v-for=\"(level3Item, level3Index) in level2Item.children\"\n              :key=\"level3Index\"\n              :menu-item=\"level3Item\"\n              small\n            />\n          </template>\n        </nav-menu-item>\n      </template>\n    </nav-menu-item>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {PropType} from \"vue\";\n\ndefineProps({\n  menu: {\n    type: Array as PropType<Array<App.GlobalMenuOption>>,\n    default: () => []\n  }\n})\n</script>\n"
  },
  {
    "path": "src/components/navigation/NavMenuItem.vue",
    "content": "<template>\n  <div>\n    <v-list-item\n      v-if=\"!(menuItem.children && menuItem.children.length>0)\"\n      :to=\"menuItem.routePath\"\n      density=\"comfortable\"\n      active-color=\"primary\"\n      link\n    >\n      <template v-slot:prepend>\n        <v-icon :size=\"small?'x-small':'default'\"\n                :class=\"{  'same-size':small }\">\n          {{ menuItem.icon || 'mdi-circle-medium' }}\n        </v-icon>\n      </template>\n      <v-list-item-title>\n        {{ $t(menuItem.label) }}\n      </v-list-item-title>\n    </v-list-item>\n\n    <v-list-group\n      v-else\n      active-color=\"primary\"\n      :subgroup=\"subgroup\"\n      collapse-icon=\"mdi-menu-up\"\n      expand-icon=\"mdi-menu-down\"\n    >\n\n      <template v-slot:activator=\"{ props }\">\n\n        <v-list-item density=\"comfortable\" v-bind=\"props\"\n                     :title=\"$t(menuItem.label)\">\n          <template v-slot:prepend>\n            <v-icon v-if=\"!subgroup\" :size=\"small ?'x-small':'default'\" :class=\"{'same-size':small }\">\n              {{ menuItem.icon || 'mdi-circle-medium' }}\n            </v-icon>\n          </template>\n        </v-list-item>\n      </template>\n\n      <slot></slot>\n    </v-list-group>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {PropType} from \"vue\";\n\ndefineProps({\n  menuItem: {\n    type: Object as PropType<App.GlobalMenuOption>,\n    default: () => {\n    }\n  },\n  subgroup: {\n    type: Boolean,\n    default: false\n  },\n  small: {\n    type: Boolean,\n    default: false\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n@use 'sass:map';\n@use '@/assets/scss/settings';\n\n.same-size {\n  width: calc(var(--v-icon-size-multiplier) * #{map.get(settings.$icon-sizes, 'default')});\n}\n\n.v-list-group :deep(.v-list-item ) {\n  padding-inline-start: settings.$spacer !important;\n}\n\n.v-list-group--subgroup :deep(.v-list-group__items .v-list-item ) {\n  padding-inline-start: settings.$spacer * 5 !important;\n}\n\n</style>\n"
  },
  {
    "path": "src/components/provider/DialogProvider.tsx",
    "content": "import {defineComponent, provide, VNodeChild, ref, reactive, h} from 'vue'\nimport {createId} from 'seemly'\nimport {VCard, VDialog, VCardText, VCardActions, VCardTitle, VSpacer, VBtn} from 'vuetify/components'\nimport {render} from '@/utils'\n\n\ninterface ContentType {\n  title?: string,\n  cancelText?: string,\n  persistent?: boolean\n  confirmText?: string,\n  confirmColor?: string,\n  main: string | (() => VNodeChild)\n  cancel?: () => void,\n  confirm?: () => void,\n}\n\nexport const DialogInjectKey = \"DialogInjectKey \"\n\nexport interface DialogReactive {\n  key: string,\n  content: ContentType\n  _modelValue: boolean,\n  _onClose: (key: string) => void\n  close: () => void\n  confirmLoading: (loading: boolean) => void\n  _confirmLoading: boolean\n}\n\n\nexport interface DialogApiInjection {\n  show: (content: ContentType) => DialogReactive\n  closeAll: () => void\n}\n\nexport default defineComponent({\n  name: \"dialogProvider\",\n  setup() {\n    const dialogListRef = ref<DialogReactive[]>([])\n    const dialogRefs = ref<Record<string, DialogReactive>>({})\n\n\n    const api: DialogApiInjection = {\n      show(content: ContentType): DialogReactive {\n        return create(content)\n      },\n      closeAll() {\n\n        dialogListRef.value = []\n        dialogRefs.value = {}\n      }\n    }\n    provide(DialogInjectKey, api)\n\n    function create(content: ContentType): DialogReactive {\n      const dialogReactive = reactive<DialogReactive>({\n        key: createId(),\n        content: {\n          persistent: true,\n          ...content\n        },\n        _modelValue: true,\n        _confirmLoading: false,\n        _onClose: (key: string) => {\n          dialogListRef.value.splice(\n            dialogListRef.value.findIndex((message) => message.key === key),\n            1\n          )\n          delete dialogRefs.value[key]\n        },\n        confirmLoading(loading) {\n          this._confirmLoading = loading\n        },\n        close() {\n          this._modelValue = false\n          setTimeout(() => {\n            this._onClose(this.key)\n          }, 1200)\n        }\n      })\n      dialogListRef.value.push(dialogReactive)\n      // @ts-ignore\n      return dialogReactive\n    }\n\n    return Object.assign(\n      {\n        dialogRefs,\n        dialogList: dialogListRef,\n      },\n      api\n    )\n  },\n  render() {\n    return (\n      <>\n        {this.$slots.default?.()}\n        {this.dialogList.length > 0 ? (\n          <>\n            {this.dialogList.map((msg: DialogReactive) => {\n              return (\n                <VDialog\n                  ref={\n                    ((inst: DialogReactive) => {\n                      if (inst) {\n                        this.dialogRefs[msg.key] = inst\n                      }\n                    }) as () => void\n                  }\n                  v-model={msg._modelValue}\n                  maxWidth={290}\n                  persistent={!!msg.content.persistent}\n                >\n                  <VCard>\n                    <VCardTitle class={'headline'}>\n                      {msg.content.title ?? this.$t('common.title')}\n                    </VCardTitle>\n                    <VCardText>\n                      {render(msg.content.main)}\n                    </VCardText>\n                    <VCardActions>\n                      <VSpacer></VSpacer>\n                      <VBtn {...{\n                        'onClick': () => {\n                          msg.close()\n                          msg.content.cancel?.()\n                        }\n                      }} >{msg.content.cancelText ?? this.$t('common.cancel')}</VBtn>\n                      <VBtn loading={msg._confirmLoading} color={msg.content?.confirmColor ?? 'error'}{...{\n                        'onClick': () => {\n                          msg.content.confirm ? msg.content.confirm() : msg.close()\n                        }\n                      }} >{msg.content.confirmText ?? this.$t('common.confirm')}</VBtn>\n                    </VCardActions>\n                  </VCard>\n\n                </VDialog>\n              )\n            })}\n          </>\n        ) : null}\n      </>\n    )\n  }\n})\n\nexport function useDialog(): DialogApiInjection {\n  const api = inject(DialogInjectKey, null)\n  if (api === null) {\n    throw new Error('not outer <snackbar-provider> found')\n  }\n  return api\n}\n"
  },
  {
    "path": "src/components/provider/LoadingOverlyProvider.tsx",
    "content": "import {defineComponent, ref} from 'vue'\nimport {VOverlay, VProgressCircular} from 'vuetify/components'\n\nexport const LoadingOverlyInjectKey = \"LoadingOverlyInjectKey \"\n\nexport interface LoadingOverlyApiInjection {\n  show: () => void\n  hide: () => void\n}\n\nexport default defineComponent({\n  name: \"loadingOverlyProvider\",\n  setup() {\n    const model = ref(false)\n    const api: LoadingOverlyApiInjection =\n      {\n        show: () => {\n          model.value = true\n        },\n        hide: () => {\n          model.value = false\n        }\n      }\n    provide(LoadingOverlyInjectKey, api)\n    return {\n      model\n    }\n  },\n  render() {\n    return (\n      <>\n        {this.$slots.default?.()}\n        <VOverlay\n          v-model={this.model}\n          class={['align-center', 'justify-center']}\n        >\n          <VProgressCircular\n            color=\"primary\"\n            indeterminate\n            size=\"64\"\n          ></VProgressCircular>\n        </VOverlay>\n      </>\n    )\n  }\n})\n\nexport function useLoadingOverly(): LoadingOverlyApiInjection {\n  const api = inject(LoadingOverlyInjectKey, null)\n  if (api === null) {\n    throw new Error('not outer <loading-overly-provider> found')\n  }\n  return api\n}\n"
  },
  {
    "path": "src/components/provider/LoadingProgressLine.tsx",
    "content": "import {defineComponent, ref} from 'vue'\nimport {VProgressLinear} from 'vuetify/components'\n\nexport const LoadingProgressLineInjectKey = \"LoadingProgressLineInjectKey \"\n\nexport interface LoadingProgressLineApiInjection {\n  show: () => void\n  hide: () => void\n}\n\nexport default defineComponent({\n  name: \"loadingProgressProvider\",\n  setup() {\n    const model = ref(false)\n    const api: LoadingProgressLineApiInjection =\n      {\n        show: () => {\n          model.value = true\n        },\n        hide: () => {\n          model.value = false\n        }\n      }\n    const theme = useThemeStore()\n    provide(LoadingProgressLineInjectKey, api)\n    return {\n      model,\n      theme\n    }\n  },\n  render() {\n    return (\n      <>\n        {this.$slots.default?.()}\n        <VProgressLinear\n          active={this.model}\n          indeterminate={true}\n          absolute={true}\n          class={[this.theme.isToolbarDetached ? 'mt-3' : null, 'position-fixed']}\n          style=\"top: var(--v-layout-top);z-index: 1000\"\n          color=\"primary-lighten-1\"\n        ></VProgressLinear>\n      </>\n    )\n  }\n})\n\nexport function useLoadingProgressLine(): LoadingProgressLineApiInjection {\n  const api = inject(LoadingProgressLineInjectKey, null)\n  if (api === null) {\n    throw new Error('not outer <loading-progress-provider> found')\n  }\n  return api\n}\n"
  },
  {
    "path": "src/components/provider/SnackbarProvider.tsx",
    "content": "import {defineComponent, provide, VNodeChild, ref, reactive, h} from 'vue'\nimport {VSnackbar} from 'vuetify/components'\nimport {createId} from 'seemly'\nimport {render} from '@/utils'\n\n\ntype ContentType = string | (() => VNodeChild)\n\nexport const SnackBarInjectKey = \"SnackBarInjectKey \"\n\ninterface SnackBarReactive {\n  key: string,\n  content: ContentType\n  modelValue: boolean,\n  options?: Snackbar,\n  onClose: (key: string) => void\n}\n\n\nexport interface SnackBarApiInjection {\n  info: (content: ContentType, options?: Snackbar) => SnackBarReactive\n  success: (content: ContentType, options?: Snackbar) => SnackBarReactive\n  warning: (content: ContentType, options?: Snackbar) => SnackBarReactive\n  error: (content: ContentType, options?: Snackbar) => SnackBarReactive\n}\n\nexport default defineComponent({\n  name: \"snackbarProvider\",\n  setup() {\n    const snackBarListRef = ref<SnackBarReactive[]>([])\n    const snackBarRefs = ref<Record<string, SnackBarReactive>>({})\n\n    const api: SnackBarApiInjection = {\n      info(content: ContentType, options?: Snackbar): SnackBarReactive {\n        return create(content, {...options, color: \"info\", timeout: 1000})\n      },\n      success(content: ContentType, options?: Snackbar): SnackBarReactive {\n        return create(content, {...options, color: \"success\", timeout: 1000})\n      },\n      error(content: ContentType, options?: Snackbar): SnackBarReactive {\n        return create(content, {...options, color: \"error\", timeout: 2000})\n      },\n      warning(content: ContentType, options?: Snackbar): SnackBarReactive {\n        return create(content, {...options, color: \"warning\", timeout: 1000})\n      },\n    }\n    provide(SnackBarInjectKey, api)\n\n    function create(content: ContentType, options?: Snackbar): SnackBarReactive {\n      const snackBarReactive = reactive<SnackBarReactive>({\n        key: createId(),\n        content,\n        options: {\n          timeout: 2000,\n          ...options,\n        },\n        modelValue: true,\n        onClose: (key: string) => {\n          snackBarListRef.value.splice(\n            snackBarListRef.value.findIndex((message) => message.key === key),\n            1\n          )\n          delete snackBarRefs.value[key]\n        }\n      })\n      snackBarListRef.value.push(snackBarReactive)\n      // @ts-ignore\n      return snackBarReactive\n    }\n\n    return Object.assign(\n      {\n        snackBarRefs,\n        snackBarList: snackBarListRef,\n      },\n      api\n    )\n  },\n  render() {\n    return (\n      <>\n        {this.$slots.default?.()}\n        {this.snackBarList.length > 0 ? (\n          <>\n            {this.snackBarList.map((msg: SnackBarReactive) => {\n              // @ts-ignore\n              return (\n                <VSnackbar\n                  ref={\n                    ((inst: SnackBarReactive) => {\n                      if (inst) {\n                        this.snackBarRefs[msg.key] = inst\n                      }\n                    }) as () => void\n                  }\n                  v-model={msg.modelValue}\n                  {...msg.options}\n                  {...{\n                    'onUpdate:modelValue': (val: boolean) => {\n                      if (!val) {\n                        setTimeout(() => {\n                          msg.onClose(msg.key)\n                        }, msg.options ? Number(msg.options.timeout) + 200 : 1000)\n                      }\n                    }\n                  }}\n\n                >\n                  {render(msg.content)}\n                </VSnackbar>\n              )\n            })}\n          </>\n        ) : null}\n      </>\n    )\n  }\n})\n\nexport function useSnackBar(): SnackBarApiInjection {\n  const api = inject(SnackBarInjectKey, null)\n  if (api === null) {\n    throw new Error('not outer <snackbar-provider> found')\n  }\n  return api\n}\n"
  },
  {
    "path": "src/components/provider/VuetifyProvider.vue",
    "content": "<template>\n  <loading-overly-provider>\n    <snackbar-provider>\n      <dialog-provider>\n        <slot/>\n        <vuetify-provider-content/>\n      </dialog-provider>\n    </snackbar-provider>\n  </loading-overly-provider>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useSnackBar, useDialog} from \"@/components/provider\";\nimport {useLoadingOverly} from \"@/components/provider\";\nimport SnackbarProvider from \"@/components/provider/SnackbarProvider\";\nimport LoadingOverlyProvider from \"@/components/provider/LoadingOverlyProvider\";\nimport DialogProvider from \"@/components/provider/DialogProvider\";\n\nfunction registerTools() {\n  window.$snackBar = useSnackBar()\n  window.$loadingOverly = useLoadingOverly()\n  window.$dialog = useDialog()\n}\n\nconst VuetifyProviderContent = defineComponent({\n  name: 'VuetifyProviderContent',\n  setup() {\n    registerTools()\n  },\n  render() {\n    return h('div');\n  }\n});\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/components/provider/index.ts",
    "content": "export * from './SnackbarProvider'\nexport * from './LoadingOverlyProvider'\nexport * from './LoadingProgressLine'\nexport * from './DialogProvider'\n"
  },
  {
    "path": "src/components/toolbar/ToolbarLanguage.vue",
    "content": "<template>\n  <v-menu\n  >\n    <template v-slot:activator=\"{ props }\">\n      <v-btn text v-bind=\"props\">\n        <flag-icon :round=\"smAndDown\" :flag=\"currentLocale.flag\"></flag-icon>\n        <span v-show=\"mdAndUp&& showLabel\"\n              class=\"ml-1\"\n        > {{ currentLocale.label }}</span>\n        <v-icon v-if=\"showArrow\" right>mdi-chevron-down</v-icon>\n      </v-btn>\n    </template>\n\n    <v-list nav density=\"comfortable\">\n      <v-list-item v-for=\"locale in availableLocales\" :key=\"locale.code\" @click=\"setLocale(locale.code)\">\n        <v-list-item-title class=\"d-flex justify-center align-center\">\n          <flag-icon class=\"mr-1\" :flag=\"locale.flag\"></flag-icon>\n          {{ locale.label }}\n        </v-list-item-title>\n      </v-list-item>\n    </v-list>\n  </v-menu>\n</template>\n\n<script setup lang=\"ts\">\nimport FlagIcon from '../common/FlagIcon.vue'\nimport {computed} from \"vue\";\nimport {useI18n} from 'vue-i18n'\nimport configs from \"@/configs\";\nimport {useDisplay} from 'vuetify'\n\ndefineProps({\n  showArrow: {\n    type: Boolean,\n    default: false\n  },\n  // Show the country label\n  showLabel: {\n    type: Boolean,\n    default: true\n  }\n})\n\nconst {smAndDown, mdAndUp} = useDisplay()\n\nconst {locale} = useI18n()\nconst {availableLocales: locales} = configs.locales\n\nconst currentLocale = computed(() => {\n  return locales.find((i: any) => i.code === locale.value)\n})\nconst availableLocales = computed(() => {\n  return locales.filter((i: any) => i.code !== locale.value)\n})\n\nconst setLocale = (use: string) => {\n  locale.value = use\n}\n</script>\n"
  },
  {
    "path": "src/components/toolbar/ToolbarNotifications.vue",
    "content": "<template>\n  <v-menu offset-y left transition=\"slide-y-transition\">\n    <template v-slot:activator=\"{ props }\">\n      <v-btn icon class=\"text-none\" v-bind=\"props\">\n        <v-badge content=\"6\" color=\"primary\" bordered>\n          <v-icon>mdi-bell-outline</v-icon>\n        </v-badge>\n      </v-btn>\n    </template>\n\n    <!-- dropdown card -->\n    <v-card>\n      <v-list three-line dense max-width=\"400\">\n        <v-list-subheader class=\"pa-2 font-weight-bold\">Notifications</v-list-subheader>\n        <div v-for=\"(item,index) in items\" :key=\"index\">\n          <v-divider v-if=\"index > 0 && index < items.length\" inset></v-divider>\n          <v-list-item :title=\"item.title\" :subtitle=\"item.subtitle\">\n            <template v-slot:prepend>\n              <v-avatar :icon=\"item.icon\" size=\"32\" :color=\"item.color\"></v-avatar>\n            </template>\n            <v-list-item-action class=\"align-self-center\" v-text=\"item.time\">\n            </v-list-item-action>\n          </v-list-item>\n        </div>\n      </v-list>\n\n      <div class=\"text-center py-2\">\n        <v-btn small>See all</v-btn>\n      </div>\n    </v-card>\n  </v-menu>\n</template>\n\n<script lang=\"ts\" setup>\nconst items = ref([\n  {\n    title: 'Brunch this weekend?',\n    color: 'primary',\n    icon: 'mdi-account-circle',\n    subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint, repudiandae?',\n    time: '3 min'\n  },\n  {\n    title: 'Summer BBQ',\n    color: 'success',\n    icon: 'mdi-email-outline',\n    subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint, repudiandae?',\n    time: '3 min'\n  },\n  {\n    title: 'Oui oui',\n    color: 'teal lighten-1',\n    icon: 'mdi-airplane-landing',\n    subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint, repudiandae?',\n    time: '4 min'\n  },\n  {\n    title: 'Disk capacity is at maximum',\n    color: 'teal accent-3',\n    icon: 'mdi-server',\n    subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint, repudiandae?',\n    time: '3 hr'\n  },\n  {\n    title: 'Recipe to try',\n    color: 'blue-grey lighten-2',\n    icon: 'mdi-noodles',\n    subtitle: 'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sint, repudiandae?',\n    time: '8 hr'\n  }\n])\n</script>\n"
  },
  {
    "path": "src/components/toolbar/ToolbarUser.vue",
    "content": "<template>\n  <v-menu offset-y left transition=\"slide-y-transition\">\n    <template v-slot:activator=\"{ props }\">\n      <v-btn icon size=\"small\" class=\"elevation-2\" v-bind=\"props\">\n\n        <v-badge\n          color=\"success\"\n          dot\n          bordered\n        >\n          <v-avatar size=\"40\" :class=\"{'bg-grey':!userInfo.userAvatar}\">\n            <svg-icon v-if=\"!!userInfo.userAvatar\" :name=\"userInfo.userAvatar\"></svg-icon>\n          </v-avatar>\n        </v-badge>\n      </v-btn>\n    </template>\n\n    <!-- user menu list -->\n    <v-list dense nav>\n      <v-list-item v-if=\"!isLogin\">\n        <v-list-item-title>{{ $t('usermenu.notSignin') }}</v-list-item-title>\n      </v-list-item>\n      <v-list-item\n        v-else\n        v-for=\"(item, index) in menu\"\n        :key=\"index\"\n        :to=\"item.link\"\n        link\n      >\n        <template v-slot:prepend>\n          <v-icon size=\"small\" :icon=\"item.icon\"></v-icon>\n        </template>\n        <v-list-item-title>{{ $t(item.key) }}</v-list-item-title>\n      </v-list-item>\n\n      <v-divider class=\"my-1\"></v-divider>\n\n      <v-list-item @click=\"logout\" prepend-icon=\"mdi-logout-variant\" :title=\"$t('menu.logout')\">\n      </v-list-item>\n    </v-list>\n\n\n  </v-menu>\n</template>\n\n<script setup lang=\"ts\">\nimport {reactive} from \"vue\";\n\nconst menu = reactive(\n  [\n    {icon: 'mdi-account-box-outline', key: 'menu.profile', link: '/apps/manager-user/edit'},\n    {icon: 'mdi-format-list-checkbox', key: 'menu.todo', link: '/apps/todo'},\n    {icon: 'mdi-email-outline', key: 'menu.board', link: '/apps/board'},\n    {icon: 'mdi-forum-outline', key: 'menu.chat', link: '/apps/chat-channel/'}\n  ]\n)\n\nconst auth = useAuthStore();\nconst {isLogin, userInfo} = storeToRefs(auth)\nconst logout = () => {\n  window.$dialog?.show({\n    title: 'Logo out',\n    main: 'Are you sure logo out?',\n    confirm: () => {\n      window.$loadingOverly?.show()\n      setTimeout(() => {\n        auth.resetAuthStore()\n      }, 1000)\n    }\n\n  })\n}\n</script>\n"
  },
  {
    "path": "src/composables/events.ts",
    "content": "import { useEventListener } from '@vueuse/core';\nimport {useThemeStore} from \"@/store\";\n\n\nexport function useGlobalEvents() {\n  const appConifg= useThemeStore();\n\n  useEventListener(window, 'beforeunload', () => {\n    appConifg.cacheThemeSettings();\n  });\n}\n"
  },
  {
    "path": "src/composables/index.ts",
    "content": "export * from './router';\nexport * from './system';\n"
  },
  {
    "path": "src/composables/router.ts",
    "content": "import { useRouter } from 'vue-router';\nimport type { RouteLocationRaw } from 'vue-router';\nimport { router as globalRouter, routeName } from '@/router';\n\n/**\n * 路由跳转\n * @param inSetup - 是否在vue页面/组件的setup里面调用，在axios里面无法使用useRouter和useRoute\n */\nexport function useRouterPush(inSetup = true) {\n  const router = inSetup ? useRouter() : globalRouter;\n  const route = globalRouter.currentRoute;\n\n  /**\n   * 路由跳转\n   * @param to - 需要跳转的路由\n   * @param newTab - 是否在新的浏览器Tab标签打开\n   */\n  function routerPush(to: RouteLocationRaw, newTab = false) {\n    if (newTab) {\n      const routerData = router.resolve(to);\n      window.open(routerData.href, '_blank');\n    } else {\n      router.push(to);\n    }\n  }\n\n  /** 返回上一级路由 */\n  function routerBack() {\n    router.go(-1);\n  }\n\n  /**\n   * 跳转首页\n   * @param newTab - 在新的浏览器标签打开\n   */\n  function toHome(newTab = false) {\n    routerPush({ name: routeName('root') }, newTab);\n  }\n\n  function toLogin(loginModule?: EnumType.LoginModuleKey, redirectUrl?: string) {\n    const module: EnumType.LoginModuleKey = loginModule || 'sign-in';\n    const routeLocation: RouteLocationRaw = {\n      name: routeName('login'),\n      params: { module }\n    };\n    const redirect = redirectUrl || route.value.fullPath;\n    Object.assign(routeLocation, { query: { redirect } });\n    routerPush(routeLocation);\n  }\n\n  function toLoginModule(module: EnumType.LoginModuleKey) {\n    const { query } = route.value;\n    routerPush({ name: routeName('login'), params: { module }, query});\n  }\n\n  function toLoginRedirect() {\n    const { query } = route.value;\n    if (query?.redirect) {\n      routerPush(query.redirect as string);\n    } else {\n      toHome();\n    }\n  }\n\n  return {\n    routerPush,\n    routerBack,\n    toHome,\n    toLogin,\n    toLoginModule,\n    toLoginRedirect\n  };\n}\n"
  },
  {
    "path": "src/composables/system.ts",
    "content": "import UAParser from 'ua-parser-js';\nimport {useAuthStore} from '@/store';\nimport {isArray, isString} from '@/utils';\nimport pkg from '~/package.json';\n\ninterface AppInfo {\n  name: string;\n  title: string;\n  desc: string;\n  version: string,\n}\n\ninterface Package {\n  name: string;\n  version: string;\n}\n\nconst pkgWithType = pkg as Package;\n\nexport function useAppInfo(): AppInfo {\n  const {VITE_APP_NAME: name, VITE_APP_TITLE: title, VITE_APP_DESC: desc} = import.meta.env;\n\n  return {\n    name,\n    title,\n    desc,\n    version: pkgWithType.version\n  };\n}\n\n/** 获取设备信息 */\nexport function useDeviceInfo() {\n  const parser = new UAParser();\n  const result = parser.getResult();\n  return result;\n}\n\n/** 权限判断 */\nexport function usePermission() {\n  const auth = useAuthStore();\n\n  function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {\n    const {userRole} = auth.userInfo;\n\n    let has = userRole === 'admin';\n    if (!has) {\n      if (isArray(permission)) {\n        has = (permission as Auth.RoleType[]).includes(userRole);\n      }\n      if (isString(permission)) {\n        has = (permission as Auth.RoleType) === userRole;\n      }\n    }\n    return has;\n  }\n\n  return {\n    hasPermission\n  };\n}\n"
  },
  {
    "path": "src/configs/currencies.ts",
    "content": "const config: CurrencyConfig.Config = {\n  currency: {\n    label: 'USD',\n    decimalDigits: 2,\n    decimalSeparator: '.',\n    thousandsSeparator: ',',\n    currencySymbol: '$',\n    currencySymbolNumberOfSpaces: 0,\n    currencySymbolPosition: 'left'\n  },\n\n  availableCurrencies: [{\n    label: 'USD',\n    decimalDigits: 2,\n    decimalSeparator: '.',\n    thousandsSeparator: ',',\n    currencySymbol: '$',\n    currencySymbolNumberOfSpaces: 0,\n    currencySymbolPosition: 'left'\n  }, {\n    label: 'EUR',\n    decimalDigits: 2,\n    decimalSeparator: '.',\n    thousandsSeparator: ',',\n    currencySymbol: '€',\n    currencySymbolNumberOfSpaces: 1,\n    currencySymbolPosition: 'right'\n  }]\n}\n\nexport default config\n"
  },
  {
    "path": "src/configs/index.ts",
    "content": "import locales from './locales'\nimport theme from './theme'\nimport currency from './currencies'\n\nconst config: Config = {\n  theme,\n  locales,\n  currency\n}\n\nexport default config\n"
  },
  {
    "path": "src/configs/locales.ts",
    "content": "import en from '../translations/en'\nimport zh from '../translations/zh'\n\nconst supported = ['en', 'zh']\nlet locale = 'en'\n\ntry {\n  const {0: browserLang} = navigator.language.split('-')\n  if (supported.includes(browserLang)) locale = browserLang\n} catch (e) {\n  console.log(e)\n}\n\nexport default {\n  locale,\n\n  fallbackLocale: 'en',\n\n  availableLocales: [{\n    code: 'en',\n    flag: 'us',\n    label: 'English',\n    messages: en\n  }, {\n    code: 'zh',\n    flag: 'cn',\n    label: '中文',\n    messages: zh\n  }]\n}\n"
  },
  {
    "path": "src/configs/service.ts",
    "content": "export const REQUEST_TIMEOUT = 60 * 1000;\n\nexport const ERROR_MSG_DURATION = 3 * 1000;\n\nexport const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT';\n\nexport const DEFAULT_REQUEST_ERROR_MSG = '请求错误~';\n\nexport const REQUEST_TIMEOUT_CODE = 'ECONNABORTED';\n\nexport const REQUEST_TIMEOUT_MSG = '请求超时~';\n\nexport const NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport const NETWORK_ERROR_MSG = '网络不可用~';\n\nexport const ERROR_STATUS = {\n  400: '400: 请求出现语法错误~',\n  401: '401: 用户未授权~',\n  403: '403: 服务器拒绝访问~',\n  404: '404: 请求的资源不存在~',\n  405: '405: 请求方法未允许~',\n  408: '408: 网络请求超时~',\n  500: '500: 服务器内部错误~',\n  501: '501: 服务器未实现请求功能~',\n  502: '502: 错误网关~',\n  503: '503: 服务不可用~',\n  504: '504: 网关超时~',\n  505: '505: http版本不支持该请求~',\n  [DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG\n};\n\nexport const NO_ERROR_MSG_CODE: (string | number)[] = [];\n\nexport const REFRESH_TOKEN_CODE: (string | number)[] = [66666];\n"
  },
  {
    "path": "src/configs/theme.ts",
    "content": "const themeConfig: ThemeConfig.Config = {\n\n  primary: '#0096c7',\n\n  followOs: true,\n\n  globalTheme: 'light', // light | dark\n\n  menuTheme: 'global', // global | light | dark\n\n  toolbarTheme: 'global', // global | light | dark\n\n  isToolbarDetached: false,\n\n  isContentBoxed: false,\n\n  isRTL: false,\n\n  dark: {\n    dark: true,\n    colors: {\n      background: '#111b27',\n      surface: '#05090c',\n      primary: '#0096c7',\n      secondary: '#829099',\n      accent: '#82B1FF',\n      error: '#FF5252',\n      info: '#2196F3',\n      success: '#4CAF50',\n      warning: '#FFC107',\n    }\n  },\n\n  // light theme colors\n  light: {\n    dark: false,\n    variables: {\n      \"high-emphasis-opacity\": 1,\n      \"border-opacity\": 0.05,\n    },\n    colors: {\n      background: '#f2f5f8',\n      surface: '#ffffff',\n      primary: '#0096c7',\n      secondary: '#a0b9c8',\n      accent: '#048ba8',\n      error: '#ef476f',\n      info: '#2196F3',\n      success: '#06d6a0',\n      \"on-success\": '#ffffff',\n      warning: '#ffd166',\n    }\n  }\n}\n\nexport default themeConfig\n\n"
  },
  {
    "path": "src/constants/business.ts",
    "content": "/** 用户性别 */\nexport const genderLabels: Record<UserManagement.GenderKey, string> = {\n  0: 'female',\n  1: 'male',\n  2: 'unknown'\n};\n\nexport const genderOptions: { value: UserManagement.GenderKey; label: string }[] = [\n  {value: '0', label: genderLabels['0']},\n  {value: '1', label: genderLabels['1']},\n  {value: '2', label: genderLabels['2']},\n];\n\n/** 用户状态 */\nexport const userStatusLabels: Record<UserManagement.UserStatusKey, string> = {\n  1: 'active',\n  2: 'disabled',\n  4: 'deleted'\n};\n\nexport const userStatusOptions: { value: UserManagement.UserStatusKey; label: string }[] = [\n  {value: '1', label: userStatusLabels['1']},\n  {value: '2', label: userStatusLabels['2']},\n  {value: '4', label: userStatusLabels['4']}\n];\n\n/** 用户状态 */\nexport const formStatusLabels: Record<FormManagement.FormStatusKey, string> = {\n  1: 'active',\n  0: 'disabled',\n};\n\nexport const formStatusOptions: { value: FormManagement.FormStatusKey; label: string }[] = [\n  {value: '1', label: formStatusLabels['1']},\n  {value: '0', label: formStatusLabels['0']},\n];\n"
  },
  {
    "path": "src/constants/index.ts",
    "content": "export * from './business';\n"
  },
  {
    "path": "src/enum/business.ts",
    "content": "export enum EnumUserRole {\n  admin = 'admin',\n  user = 'commonUser'\n}\n\nexport enum EnumLoginModule {\n  'sign-in' = 'siginIn',\n  'sign-up' = 'signUp',\n  'reset' = 'reset',\n  'verify-email' = 'verifyEmail',\n  'forgot' = 'forgotPass',\n}\n"
  },
  {
    "path": "src/enum/common.ts",
    "content": "export enum EnumContentType {\n  json = 'application/json',\n  formUrlencoded = 'application/x-www-form-urlencoded',\n  formData = 'multipart/form-data'\n}\n\nexport enum EnumDataType {\n  number = '[object Number]',\n  string = '[object String]',\n  boolean = '[object Boolean]',\n  null = '[object Null]',\n  undefined = '[object Undefined]',\n  object = '[object Object]',\n  array = '[object Array]',\n  function = '[object Function]',\n  date = '[object Date]',\n  regexp = '[object RegExp]',\n  promise = '[object Promise]',\n  set = '[object Set]',\n  map = '[object Map]',\n  file = '[object File]'\n}\n"
  },
  {
    "path": "src/enum/index.ts",
    "content": "export * from './common';\nexport * from './system';\nexport * from './business';\n"
  },
  {
    "path": "src/enum/system.ts",
    "content": "export enum EnumLayoutComponentName {\n  basic = 'basic-layout',\n  blank = 'blank-layout',\n  auth = 'auth-layout',\n  error = 'error-layout',\n  todo = 'todo-layout',\n  chat = 'chat-layout'\n}\n\nexport enum EnumThemeLayoutMode {\n  'vertical' = '左侧菜单模式',\n  'horizontal' = '顶部菜单模式',\n  'vertical-mix' = '左侧菜单混合模式',\n  'horizontal-mix' = '顶部菜单混合模式'\n}\n\nexport enum EnumThemeTabMode {\n  'chrome' = '谷歌风格',\n  'button' = '按钮风格'\n}\n\nexport enum EnumThemeHorizontalMenuPosition {\n  'flex-start' = '居左',\n  'center' = '居中',\n  'flex-end' = '居右'\n}\n\nexport enum EnumThemeAnimateMode {\n  'zoom-fade' = '渐变',\n  'zoom-out' = '闪现',\n  'fade-slide' = '滑动',\n  'fade' = '消退',\n  'fade-bottom' = '底部消退',\n  'fade-scale' = '缩放消退'\n}\n"
  },
  {
    "path": "src/filters/formatCurrency.ts",
    "content": "import configs from \"@/configs\";\n\nexport function formatCurrency(value: number, currency?: CurrencyConfig.Currency): number | string {\n  const {currency: currencyConfig} = configs\n\n  let c: CurrencyConfig.Currency\n  c = currency || currencyConfig.currency\n\n  return formatPrice(value, c)\n}\n\nexport function formatPrice(price: number, currency: CurrencyConfig.Currency) {\n  try {\n    const numberFormatted = numberFormat(\n      price,\n      currency.decimalDigits,\n      currency.decimalSeparator,\n      currency.thousandsSeparator\n    )\n\n    if (currency.currencySymbol) {\n      const priceSeparator = currency.currencySymbolNumberOfSpaces > 0\n        ? ' '.repeat(currency.currencySymbolNumberOfSpaces)\n        : ''\n      let priceParts = [numberFormatted, priceSeparator, currency.currencySymbol]\n\n      if (currency.currencySymbolPosition === 'left') {\n        priceParts = priceParts.reverse()\n      }\n\n      return priceParts.join('')\n    } else {\n      return numberFormatted\n    }\n  } catch (e) {\n    return price\n  }\n}\n\nexport function numberFormat(number: number, decimals: number, dec_point: string, thousands_sep: string) {\n  if (isNaN(number)) {\n    return number\n  }\n\n  const negative = number < 0\n\n  if (negative) number = number * -1\n\n  const str = number.toFixed(decimals ? decimals : 0).toString().split('.')\n  const parts = []\n\n  for (let i = str[0].length; i > 0; i -= 3) {\n    parts.unshift(str[0].substring(Math.max(0, i - 3), i))\n  }\n\n  str[0] = parts.join(thousands_sep ? thousands_sep : ',')\n\n  return (negative ? '-' : '') + str.join(dec_point ? dec_point : '.')\n}\n"
  },
  {
    "path": "src/filters/index.ts",
    "content": "import {App} from \"vue\";\n\nexport function registerFilters(app: App) {\n  app.config.globalProperties.$filters = {\n    formatCurrency: formatCurrency\n  }\n}\n"
  },
  {
    "path": "src/hooks/common/index.ts",
    "content": "import useContext from './useContext';\nimport useBoolean from './useBoolean';\nimport useLoading from './useLoading';\nimport useLoadingEmpty from './useLoadingEmpty';\nimport useReload from './useReload';\n\nexport { useContext, useBoolean, useLoading, useLoadingEmpty, useReload };\n"
  },
  {
    "path": "src/hooks/common/useBoolean.ts",
    "content": "import { ref } from 'vue';\n\nexport default function useBoolean(initValue = false) {\n  const bool = ref(initValue);\n\n  function setBool(value: boolean) {\n    bool.value = value;\n  }\n  function setTrue() {\n    setBool(true);\n  }\n  function setFalse() {\n    setBool(false);\n  }\n  function toggle() {\n    setBool(!bool.value);\n  }\n\n  return {\n    bool,\n    setBool,\n    setTrue,\n    setFalse,\n    toggle\n  };\n}\n"
  },
  {
    "path": "src/hooks/common/useBreadcrumb.ts",
    "content": "import {computed} from 'vue';\nimport {useRoute} from 'vue-router';\nimport {routePath} from '@/router';\nimport {useRouteStore} from '@/store';\nimport {getBreadcrumbsByPredicate} from '@/utils';\n\n\nexport default function useBreadcrumb(rootPath: Exclude<AuthRoute.AllRouteKey, 'not-found'> = 'root') {\n  const route = useRoute();\n  const routeStore = useRouteStore();\n\n\n  const breadcrumbs = computed(() =>\n    getBreadcrumbsByPredicate(menu => {\n      return !!route.matched.find(m => m.path == menu.routePath)\n    }, routeStore.menus as App.GlobalMenuOption[], routePath(rootPath))\n  );\n\n  return {\n    breadcrumbs\n  };\n}\n"
  },
  {
    "path": "src/hooks/common/useContext.ts",
    "content": "import { inject, provide } from 'vue';\nimport type { InjectionKey } from 'vue';\n\nexport default function useContext<T>(contextName = 'context') {\n  const injectKey: InjectionKey<T> = Symbol(contextName);\n\n  function useProvide(context: T) {\n    provide(injectKey, context);\n    return context;\n  }\n\n  function useInject() {\n    return inject(injectKey) as T;\n  }\n\n  return {\n    useProvide,\n    useInject\n  };\n}\n"
  },
  {
    "path": "src/hooks/common/useLoading.ts",
    "content": "import useBoolean from './useBoolean';\n\nexport default function useLoading(initValue = false) {\n  const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);\n\n  return {\n    loading,\n    startLoading,\n    endLoading\n  };\n}\n"
  },
  {
    "path": "src/hooks/common/useLoadingEmpty.ts",
    "content": "import useBoolean from './useBoolean';\n\nexport default function useLoadingEmpty(initLoading = false, initEmpty = false) {\n  const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initLoading);\n  const { bool: empty, setBool: setEmpty } = useBoolean(initEmpty);\n\n  return {\n    loading,\n    startLoading,\n    endLoading,\n    empty,\n    setEmpty\n  };\n}\n"
  },
  {
    "path": "src/hooks/common/useReload.ts",
    "content": "import { nextTick } from 'vue';\nimport useBoolean from './useBoolean';\n\nexport default function useReload() {\n  // 重载的标志\n  const { bool: reloadFlag, setTrue, setFalse } = useBoolean(true);\n\n  async function handleReload(duration = 0) {\n    setFalse();\n    await nextTick();\n\n    if (duration > 0) {\n      setTimeout(() => {\n        setTrue();\n      }, duration);\n    }\n  }\n\n  return {\n    reloadFlag,\n    handleReload\n  };\n}\n"
  },
  {
    "path": "src/hooks/index.ts",
    "content": "export * from './common';\n"
  },
  {
    "path": "src/layouts/AuthLayout.vue",
    "content": "<template>\n  <div class=\"d-flex text-center flex-column flex-md-row flex-grow-1\">\n    <v-sheet class=\"layout-side mx-auto mx-md-1 d-none d-md-flex flex-md-column justify-space-between px-2\">\n      <div class=\"mt-3 mt-md-10 pa-2\">\n        <div class=\"text-h3 font-weight-bold text-primary\">\n          {{ name }}\n        </div>\n        <div class=\"title my-2\">Welcome! Let's build amazing things together.</div>\n        <v-btn to=\"/\" class=\"my-4\">Take me back</v-btn>\n      </div>\n      <img class=\"w-100\" src=\"/images/illustrations/signin-illustration.svg\"/>\n\n    </v-sheet>\n\n    <div class=\"pa-2 pa-md-4 flex-grow-1 align-center justify-center d-flex flex-column\">\n      <div class=\"layout-content ma-auto w-100\">\n        <router-view v-slot=\"{ Component }\">\n          <v-fade-transition mode=\"out-in\">\n            <component :is=\"Component\"/>\n          </v-fade-transition>\n        </router-view>\n      </div>\n      <div class=\"overline mt-4\">{{ title }} - v1.0.0</div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {useAppInfo} from \"@/composables/system\";\n\nconst {title, name} = useAppInfo();\n</script>\n\n<style scoped>\n.layout-side {\n  width: 420px;\n}\n\n.layout-content {\n  max-width: 480px;\n}\n</style>\n"
  },
  {
    "path": "src/layouts/BlankLayout/index.vue",
    "content": "<template>\n  <router-view v-slot=\"{ Component }\">\n    <v-fade-transition mode=\"out-in\">\n      <component :is=\"Component\"/>\n    </v-fade-transition>\n  </router-view>\n</template>\n\n<script setup lang=\"ts\">\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "src/layouts/DefaultLayout.vue",
    "content": "<template>\n  <!-- Navigation -->\n  <v-navigation-drawer\n    v-model=\"drawer\"\n    floating\n    name=\"app-navigation\"\n    :theme=\"theme.menuTheme\"\n    class=\"elevation-1\"\n  >\n\n    <!-- Navigation menu info -->\n    <div class=\"pa-2\">\n      <div class=\"title font-weight-bold text-uppercase text-primary\">{{ name }}</div>\n      <div class=\"overline text-grey\">{{ version }}</div>\n    </div>\n\n    <!-- Navigation menu -->\n    <div class=\"py-1\">\n      <main-menu :menu=\"menus\"/>\n    </div>\n\n    <!-- Navigation menu footer -->\n    <template v-slot:append>\n      <!-- Footer navigation links -->\n      <div class=\"py-2 text-center\">\n        <v-btn size=\"small\"\n               :href=\"'https://next.vuetifyjs.com/en/'\"\n               flat\n        >\n          {{ $t('menu.docs') }}\n        </v-btn>\n      </div>\n\n    </template>\n  </v-navigation-drawer>\n\n\n  <side-config-menu/>\n  <!-- Toolbar -->\n  <v-app-bar\n    class=\"overflow-visible px-2\"\n    :theme=\"theme.toolbarTheme === 'global'? undefined :theme.toolbarTheme\"\n    :flat=\"theme.isToolbarDetached\"\n    :color=\"theme.isToolbarDetached?'background':undefined\"\n  >\n    <v-card class=\"flex-grow-1 d-flex\" :class=\"[theme.isToolbarDetached? 'pa-1 mt-3 mx-1' : 'pa-0 ma-0']\"\n            :flat=\"!theme.isToolbarDetached\"\n    >\n      <div class=\"d-flex flex-grow-1 align-center\">\n        <v-app-bar-nav-icon @click.stop=\"drawer = !drawer\"></v-app-bar-nav-icon>\n        <v-spacer class=\"d-none d-lg-block\"/>\n        <v-autocomplete\n          :placeholder=\"$t('menu.search')\"\n          prepend-inner-icon=\"mdi-magnify\"\n          hide-details\n          :items=\"routeStore.searchMenus\"\n          item-title=\"meta.title\"\n          item-value=\"path\"\n          clearable\n          @update:modelValue=\"searchSelect\"\n          variant=\"filled\"\n          density=\"comfortable\"\n          class=\"v-text-field-rounded\"\n          single-line\n        >\n        </v-autocomplete>\n        <v-spacer class=\"d-none\"/>\n        <toolbar-language/>\n        <div class=\"mr-1\">\n          <toolbar-notifications/>\n        </div>\n        <toolbar-user/>\n      </div>\n    </v-card>\n  </v-app-bar>\n\n  <v-main>\n    <loading-progress-provider>\n      <v-container :fluid=\"!theme.isContentBoxed\" class=\"h-100 position-relative\">\n        <router-view v-slot=\"{ Component }\">\n          <v-fade-transition mode=\"out-in\">\n            <component :is=\"Component\"/>\n          </v-fade-transition>\n        </router-view>\n      </v-container>\n\n      <v-footer app>\n        <v-spacer></v-spacer>\n        <div class=\"overline\">\n          Built with\n          <v-icon small color=\"pink\">mdi-github</v-icon>\n          <a class=\"text-decoration-none\" href=\"https://github.com/sunhao1256/lulu-admin\" target=\"_blank\">@lulu</a>\n        </div>\n      </v-footer>\n    </loading-progress-provider>\n  </v-main>\n</template>\n\n<script setup lang=\"ts\">\n\nimport LoadingProgressProvider from \"@/components/provider/LoadingProgressLine\";\nimport {computed} from 'vue'\nimport {useAppInfo, useRouterPush} from \"@/composables\";\n\nconst theme = useThemeStore()\nconst drawer = ref()\nconst routeStore = useRouteStore();\nconst menus = computed(() => routeStore.menus as App.GlobalMenuOption[]);\nconst {name, version} = useAppInfo();\nconst push = useRouterPush()\nconst searchSelect = (item: AuthRoute.Route) => {\n  if (item)\n    push.routerPush(item)\n}\n\n</script>\n\n<style scoped>\n.v-text-field-rounded :deep(.v-field__input) {\n  flex-direction: column;\n  justify-content: center;\n}\n</style>\n"
  },
  {
    "path": "src/layouts/ErrorLayout.vue",
    "content": "<template>\n  <div class=\"pa-2 pa-md-4 flex-grow-1 align-center justify-center d-flex flex-column\">\n    <router-view/>\n  </div>\n</template>\n"
  },
  {
    "path": "src/layouts/index.ts",
    "content": "const BasicLayout = () => import('./DefaultLayout.vue');\nconst BlankLayout = () => import('./BlankLayout/index.vue');\nconst AuthLayout = () => import('./AuthLayout.vue');\nconst ErrorLayout = () => import('./ErrorLayout.vue');\nconst TodoLayout= () => import('../views/todo/TodoLayout.vue');\nconst ChatLayout= () => import('../views/chart/ChatPage.vue');\n\nexport {BasicLayout, BlankLayout, AuthLayout, ErrorLayout, TodoLayout , ChatLayout};\n"
  },
  {
    "path": "src/main.ts",
    "content": "/**\n * main.ts\n *\n * Bootstraps Vuetify and other plugins then mounts the App`\n */\n\n// Components\nimport App from './App.vue'\n// Composables\nimport \"@/assets/scss/theme.scss\"\nimport \"animate.css/animate.min.css\"\n\nasync function setupApp() {\n  const app = createApp(App)\n  setupStore(app)\n  await setupRouter(app)\n\n  registerFilters(app)\n  registerPlugins(app)\n  app.mount('#app')\n}\n\nsetupApp()\n"
  },
  {
    "path": "src/plugins/animate.ts",
    "content": "export function animate(node: HTMLElement, animationName: string, callBack?: () => void) {\n\n  node.classList.add('animate__animated', `animate__${animationName}`)\n\n  function handleAnimationEnd() {\n    node.classList.remove('animate__animated', `animate__${animationName}`)\n    node.removeEventListener('animationend', handleAnimationEnd)\n    if (callBack) {\n      callBack()\n    }\n  }\n\n  node.addEventListener('animationend', handleAnimationEnd)\n}\n"
  },
  {
    "path": "src/plugins/clipboard.ts",
    "content": "export function clipboard(text: string, toastText = 'Copied to Clipboard') {\n  try {\n    navigator.clipboard.writeText(text).then(() => {\n      window.$snackBar?.info(toastText)\n    })\n  }catch (e) {\n    // In Production navigator clipboard will return undefined due to unsafe http protocol, https will fine\n    window.$snackBar?.error(\"Copied to Clipboard failed!\")\n  }\n}\n"
  },
  {
    "path": "src/plugins/index.ts",
    "content": "/**\n * plugins/user.d.ts\n *\n * Automatically included in `./src/main.ts`\n */\n\n// Plugins\nimport VueApexCharts from 'vue3-apexcharts'\nimport 'virtual:svg-icons-register';\n\n// Types\nimport type {App} from 'vue'\n\nexport function registerPlugins(app: App) {\n  app.use(vuetify)\n  app.use(vueI18n)\n  app.component('apexchart', VueApexCharts)\n\n\n}\n"
  },
  {
    "path": "src/plugins/vue-i18n.ts",
    "content": "import {createI18n} from 'vue-i18n'\nimport config from '../configs'\n\nconst {locale, availableLocales, fallbackLocale} = config.locales\n\nconst messages = {}\n\navailableLocales.forEach((l: any) => { // @ts-ignore\n  messages[l.code] = l.messages\n})\n\nconst i18n = createI18n({\n  locale,\n  fallbackLocale,\n  messages,\n  legacy: false,\n  globalInjection:true\n})\nexport default i18n\n\n\n"
  },
  {
    "path": "src/plugins/vuetify.ts",
    "content": "/**\n * plugins/vuetify.ts\n *\n * Framework documentation: https://vuetifyjs.com`\n */\n\n// Styles\nimport '@mdi/font/css/materialdesignicons.css'\nimport 'vuetify/styles'\n\n// Composables\nimport {createVuetify} from 'vuetify'\nimport configs from \"@/configs\";\nimport i18n from \"@/plugins/vue-i18n\";\n\nexport default createVuetify({\n  defaults: {\n    VTextField: {\n      variant: 'underlined',\n      'clearIcon': 'mdi-close'\n    },\n    VAutocomplete: {\n      'clearIcon': 'mdi-close',\n      'noDataText': i18n.global.t('$vuetify.dataIterator.noResultsText')\n\n    },\n    VSelect: {\n      'clearIcon': 'mdi-close'\n    },\n    VBtn: {\n      variant: 'elevated',\n    },\n  },\n  theme: {\n    themes: {\n      light: configs.theme.light,\n      dark: configs.theme.dark,\n    },\n    variations: {\n      colors: [\"primary\"],\n      lighten: 5,\n      darken: 5\n    },\n  },\n})\n"
  },
  {
    "path": "src/router/guard/dynamic.ts",
    "content": "import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';\nimport { routeName } from '@/router';\nimport { useRouteStore } from '@/store';\nimport { localStg } from '@/utils';\n\nexport async function createDynamicRouteGuard(\n  to: RouteLocationNormalized,\n  _from: RouteLocationNormalized,\n  next: NavigationGuardNext\n) {\n  const route = useRouteStore();\n  const isLogin = Boolean(localStg.get('token'));\n\n  if (!route.isInitAuthRoute) {\n    if (!isLogin) {\n      const toName = to.name as AuthRoute.AllRouteKey;\n      if (route.isValidConstantRoute(toName) && !to.meta.requiresAuth) {\n        next();\n      } else {\n        const redirect = to.fullPath;\n        next({ name: routeName('login'), query: { redirect } });\n      }\n      return false;\n    }\n\n    await route.initAuthRoute();\n\n    if (to.name === routeName('not-found')) {\n      // 动态路由没有加载导致被not-found路由捕获，等待权限路由加载好了，回到之前的路由\n      // 若路由是从根路由重定向过来的，重新回到根路由\n      const ROOT_ROUTE_NAME: AuthRoute.AllRouteKey = 'root';\n      const path = to.redirectedFrom?.name === ROOT_ROUTE_NAME ? '/' : to.fullPath;\n      next({ path, replace: true, query: to.query, hash: to.hash });\n      return false;\n    }\n  }\n\n  // 权限路由已经加载，仍然未找到，重定向到404\n  if (to.name === routeName('not-found')) {\n    next({ name: routeName('404'), replace: true });\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "src/router/guard/index.ts",
    "content": "import type {Router} from 'vue-router';\nimport {useTitle} from '@vueuse/core';\nimport {createPermissionGuard} from './permission';\nimport i18n from '@/plugins/vue-i18n'\n\nexport function createRouterGuard(router: Router) {\n  router.beforeEach(async (to, from, next) => {\n    await createPermissionGuard(to, from, next);\n  });\n  router.afterEach(to => {\n    //set document title\n    useTitle(i18n.global.t(to.meta.title));\n  });\n}\n"
  },
  {
    "path": "src/router/guard/permission.ts",
    "content": "import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';\nimport { routeName } from '@/router';\nimport { useAuthStore } from '@/store';\nimport { exeStrategyActions, localStg } from '@/utils';\nimport { createDynamicRouteGuard } from './dynamic';\n\nexport async function createPermissionGuard(\n  to: RouteLocationNormalized,\n  from: RouteLocationNormalized,\n  next: NavigationGuardNext\n) {\n  const permission = await createDynamicRouteGuard(to, from, next);\n  if (!permission) return;\n\n  if (to.meta.href) {\n    window.open(to.meta.href);\n    next({ path: from.fullPath, replace: true, query: from.query });\n    return;\n  }\n\n  const auth = useAuthStore();\n  const isLogin = Boolean(localStg.get('token'));\n  const permissions = to.meta.permissions || [];\n  const needLogin = Boolean(to.meta?.requiresAuth) || Boolean(permissions.length);\n  const hasPermission = !permissions.length || permissions.includes(auth.userInfo.userRole);\n\n  const actions: Common.StrategyAction[] = [\n    // 已登录状态跳转登录页，跳转至首页\n    [\n      isLogin && to.name === routeName('login'),\n      () => {\n        next({ name: routeName('root') });\n      }\n    ],\n    // 不需要登录权限的页面直接通行\n    [\n      !needLogin,\n      () => {\n        next();\n      }\n    ],\n    // 未登录状态进入需要登录权限的页面\n    [\n      !isLogin && needLogin,\n      () => {\n        const redirect = to.fullPath;\n        next({ name: routeName('login'), query: { redirect } });\n      }\n    ],\n    // 登录状态进入需要登录权限的页面，有权限直接通行\n    [\n      isLogin && needLogin && hasPermission,\n      () => {\n        next();\n      }\n    ],\n    [\n      // 登录状态进入需要登录权限的页面，无权限，重定向到无权限页面\n      isLogin && needLogin && !hasPermission,\n      () => {\n        next({ name: routeName('403') });\n      }\n    ]\n  ];\n\n  exeStrategyActions(actions);\n}\n"
  },
  {
    "path": "src/router/helpers/index.ts",
    "content": ""
  },
  {
    "path": "src/router/index.ts",
    "content": "import type {App} from \"vue\"\nimport { transformAuthRouteToVueRoutes } from '@/utils/router/transform';\nimport { transformRouteNameToRoutePath } from '@/utils';\nimport { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';\nimport { constantRoutes } from './routes';\nimport { createRouterGuard } from './guard';\n\nconst { VITE_HASH_ROUTE = 'N', VITE_BASE_URL } = import.meta.env;\n\nexport const router = createRouter({\n  history: VITE_HASH_ROUTE === 'Y' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),\n  routes: transformAuthRouteToVueRoutes(constantRoutes),\n});\n\nexport const routeName = (key: AuthRoute.AllRouteKey) => key;\nexport const routePath = (key: Exclude<AuthRoute.AllRouteKey, 'not-found'>) => transformRouteNameToRoutePath(key);\n\nexport async function setupRouter(app: App) {\n  app.use(router);\n  createRouterGuard(router);\n  await router.isReady();\n}\n\nexport * from './routes';\nexport * from './modules';\n"
  },
  {
    "path": "src/router/modules/dashboard.ts",
    "content": "const dashboard: AuthRoute.Route = {\n  name: 'dashboard',\n  path: '/dashboard',\n  component: 'basic',\n  children: [\n    {\n      name: 'dashboard_analytics',\n      path: '/dashboard/analytics',\n      component: 'self',\n      meta: {\n        icon: 'mdi-view-dashboard-outline',\n        title: 'menu.dashboard',\n        requiresAuth: true\n      }\n    }\n  ],\n  meta: {\n    title: 'menu.dashboard',\n    icon: 'mdi-view-dashboard-outline',\n    order: 1,\n    requiresAuth: true\n  }\n};\n\nexport default dashboard;\n"
  },
  {
    "path": "src/router/modules/index.ts",
    "content": "import { handleModuleRoutes } from '@/utils';\n\nconst modules = import.meta.glob('./**/*.ts', { eager: true }) as AuthRoute.RouteModule;\n\nexport const routes = handleModuleRoutes(modules);\n"
  },
  {
    "path": "src/router/modules/management.ts",
    "content": "const management: AuthRoute.Route = {\n    name: 'apps',\n    path: '/apps',\n    component: 'basic',\n    children: [\n      {\n        name: 'apps_manager-user',\n        path: '/apps/manager-user',\n        component: 'blank',\n        children: [\n          {\n            name: 'apps_manager-user_list',\n            path: '/apps/manager-user/list',\n            component: 'self',\n            meta: {\n              title: 'menu.usersList',\n              requiresAuth: true\n            }\n          },\n          {\n            name: 'apps_manager-user_edit',\n            path: '/apps/manager-user/edit',\n            component: 'self',\n            meta: {\n              title: 'menu.usersEdit',\n              dynamicPath: '/apps/manager-user/edit/:id?',\n              requiresAuth: true\n            }\n          }\n        ],\n        meta: {\n          title: 'menu.users',\n          icon: 'mdi-account-multiple-outline',\n          requiresAuth: true,\n          order: 2,\n        }\n      },\n      {\n        name: 'apps_board',\n        path: '/apps/board',\n        component: 'self',\n        meta: {\n          title: 'menu.board',\n          icon: 'mdi-view-column-outline',\n          order: 1,\n          requiresAuth: true,\n        }\n      },\n      {\n        name: 'apps_todo',\n        path: '/apps/todo',\n        component: 'todo',\n        children: [\n          {\n            name: 'apps_todo_tasks',\n            path: '/apps/todo/tasks',\n            component: 'self',\n            meta: {\n              title: 'todo.tasks',\n              requiresAuth: true,\n              hide: true,\n            }\n\n          },\n          {\n            name: 'apps_todo_completed',\n            path: '/apps/todo/completed',\n            component: 'self',\n            meta: {\n              title: 'todo.completed',\n              requiresAuth: true,\n              hide: true,\n            }\n          },\n          {\n            name: 'apps_todo_label',\n            path: '/apps/todo/label',\n            component: 'self',\n            meta: {\n              title: 'todo.labels',\n              hide: true,\n              requiresAuth: true,\n              dynamicPath: '/apps/todo/label/:id'\n            }\n\n          }\n        ],\n        meta: {\n          title: 'menu.todo',\n          icon: 'mdi-format-list-checkbox',\n          requiresAuth: true,\n          order: 1\n        }\n      }\n    ],\n    meta: {\n      title: 'menu.apps',\n      requiresAuth: true,\n    }\n  }\n;\n\nexport default management;\n"
  },
  {
    "path": "src/router/modules/pages.ts",
    "content": "const pages: AuthRoute.Route = {\n  name: 'pages',\n  path: '/pages',\n  component: 'error',\n  children: [\n    {\n      name: 'pages_error',\n      path: '/pages/error',\n      meta: {\n        title: 'menu.errorPages',\n        icon: 'mdi-file-cancel-outline',\n        order: 1,\n        requiresAuth: true\n      },\n      children: [\n        {\n          name: 'pages_error_notfound',\n          path: '/pages/error/notfound',\n          component: 'self',\n          meta: {\n            icon: 'mdi-file-outline',\n            title: 'menu.errorNotFound',\n          },\n        },\n        {\n          name: 'pages_error_unexpected',\n          path: '/pages/error/unexpected',\n          component: 'self',\n          meta: {\n            icon: 'mdi-file-outline',\n            title: 'menu.errorUnexpected',\n          },\n        }\n      ]\n    },\n  ],\n  meta: {\n    title: 'menu.pages',\n    order: 1,\n  }\n};\n\nexport default pages;\n"
  },
  {
    "path": "src/router/routes/index.ts",
    "content": "import {getLoginModuleRegExp} from '@/utils';\n\nexport const ROOT_ROUTE: AuthRoute.Route = {\n  name: 'root',\n  path: '/',\n  redirect: import.meta.env.VITE_ROUTE_HOME_PATH,\n  meta: {\n    title: 'Root'\n  }\n};\n\nexport const constantRoutes: AuthRoute.Route[] = [\n  ROOT_ROUTE,\n  {\n    name: 'login',\n    path: '/login',\n    component: 'self',\n    props: route => {\n      const moduleType = (route.params.module as EnumType.LoginModuleKey) || 'sign-in';\n      return {\n        module: moduleType\n      };\n    },\n    meta: {\n      title: 'login.title',\n      dynamicPath: `/login/:module(${getLoginModuleRegExp()})?`,\n      singleLayout: 'auth'\n    }\n  },\n  {\n    name: '403',\n    path: '/403',\n    component: 'self',\n    meta: {\n      title: 'error.forbidden',\n      singleLayout: 'blank'\n    }\n  },\n  {\n    name: '404',\n    path: '/404',\n    component: 'self',\n    meta: {\n      title: 'error.notfound',\n      singleLayout: 'error'\n    }\n  },\n  {\n    name: '500',\n    path: '/500',\n    component: 'self',\n    meta: {\n      title: 'error.other',\n      singleLayout: 'error'\n    }\n  },\n  {\n    name: 'not-found',\n    path: '/:pathMatch(.*)*',\n    component: 'blank',\n    meta: {\n      title: 'error.notfound',\n      singleLayout: 'blank'\n    }\n  }\n];\n"
  },
  {
    "path": "src/service/api/auth.ts",
    "content": "import { mockRequest } from '../request';\n\nexport function fetchSmsCode(phone: string) {\n  return mockRequest.post<boolean>('/getSmsCode', { phone });\n}\n\nexport function fetchLogin(userName: string, password: string) {\n  return mockRequest.post<ApiAuth.Token>('/login', { userName, password });\n}\n\nexport function fetchUserInfo() {\n  return mockRequest.get<ApiAuth.UserInfo>('/getUserInfo');\n}\n\nexport function fetchUserRoutes(userId: string) {\n  return mockRequest.post<ApiRoute.Route>('/getUserRoutes', { userId });\n}\n\nexport function fetchUpdateToken(refreshToken: string) {\n  return mockRequest.post<ApiAuth.Token>('/updateToken', { refreshToken });\n}\n"
  },
  {
    "path": "src/service/api/chat.ts",
    "content": "import {mockRequest} from '../request';\n\nexport function fetchMessage() {\n  return mockRequest.post<ApiChatManagement.message>(\"/getMessage\");\n}\n"
  },
  {
    "path": "src/service/api/index.ts",
    "content": "export * from './auth';\nexport * from './chat';\nexport * from './management';\n"
  },
  {
    "path": "src/service/api/management.adapter.ts",
    "content": "export function adapterOfFetchUserList(data: ApiCommon.PageResult<ApiUserManagement.User[]> | null): ApiCommon.PageResult<UserManagement.User[]> {\n  if (!data) return {\n    pageNo: 1,\n    pageSize: 20,\n    list: [],\n    total: 0,\n  };\n\n  return {\n    total: data.total,\n    pageNo: data.pageNo,\n    pageSize: data.pageSize,\n    list: data.list.map((item, index) => {\n      const user: UserManagement.User = {\n        role: 'user',\n        ...item\n      };\n      return user;\n    })\n  }\n}\n\nexport function adapterOfFetchUser(data: ApiUserManagement.User | null): UserManagement.User | null {\n  if (!data) return null;\n\n  const user: UserManagement.User = {\n    role: 'user',\n    ...data\n  };\n  return user\n}\n\nexport function deriveFetchListAdapter<T, Y extends T>(transfer: (t: T) => Y) {\n  return (data: ApiCommon.PageResult<T[]> | null): ApiCommon.PageResult<Y[]> => {\n    if (!data) return {\n      pageNo: 1,\n      pageSize: 20,\n      list: [],\n      total: 0,\n    };\n    return {\n      total: data.total,\n      pageNo: data.pageNo,\n      pageSize: data.pageSize,\n      list: data.list.map((item, index) => {\n        const user: Y = transfer(item)\n        return user;\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "src/service/api/management.ts",
    "content": "import {adapter} from '@/utils';\nimport {mockRequest} from '../request';\nimport {adapterOfFetchUserList, adapterOfFetchUser, deriveFetchListAdapter} from './management.adapter';\n\nexport const fetchUserList = async () => {\n  const data = await mockRequest.post<ApiCommon.PageResult<ApiUserManagement.User[]> | null>('/getAllUserList');\n  return adapter(adapterOfFetchUserList, data);\n};\nexport const fetchUser = async (id: string) => {\n  const data = await mockRequest.post<ApiUserManagement.User | null>(`/getUser/${id}`);\n  return adapter(adapterOfFetchUser, data);\n};\n\nexport const fetchFormList = async () => {\n  const data = await mockRequest.post<ApiCommon.PageResult<ApiForm.Form[]> | null>(`/getAllFormList`);\n  return adapter(deriveFetchListAdapter<ApiForm.Form, FormManagement.Form>(apiFrom => {\n    const form: FormManagement.Form = {\n      ...apiFrom\n    }\n    return form;\n  }), data);\n};\n"
  },
  {
    "path": "src/service/index.ts",
    "content": "export * from './api';\n"
  },
  {
    "path": "src/service/request/helpers.ts",
    "content": "import type { AxiosRequestConfig } from 'axios';\nimport { useAuthStore } from '@/store';\nimport { localStg } from '@/utils';\nimport { fetchUpdateToken } from '../api';\n\n/**\n * 刷新token\n * @param axiosConfig - token失效时的请求配置\n */\nexport async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {\n  const { resetAuthStore } = useAuthStore();\n  const refreshToken = localStg.get('refreshToken') || '';\n  const { data } = await fetchUpdateToken(refreshToken);\n  if (data) {\n    localStg.set('token', data.token);\n    localStg.set('refreshToken', data.refreshToken);\n\n    const config = { ...axiosConfig };\n    if (config.headers) {\n      config.headers.Authorization = data.token;\n    }\n    return config;\n  }\n\n  resetAuthStore();\n  return null;\n}\n"
  },
  {
    "path": "src/service/request/index.ts",
    "content": "import { getServiceEnvConfig } from '~/.env-config';\nimport { createRequest } from './request';\n\nconst { url, urlPattern, secondUrl, secondUrlPattern } = getServiceEnvConfig(import.meta.env);\n\nconst isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'Y';\n\nexport const request = createRequest({ baseURL: isHttpProxy ? urlPattern : url });\n\nexport const secondRequest = createRequest({ baseURL: isHttpProxy ? secondUrlPattern : secondUrl });\n\nexport const mockRequest = createRequest({ baseURL: '/mock' });\n"
  },
  {
    "path": "src/service/request/instance.ts",
    "content": "import axios from 'axios';\nimport type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios';\nimport { REFRESH_TOKEN_CODE } from '@/configs/service';\nimport {\n  localStg,\n  handleAxiosError,\n  handleBackendError,\n  handleResponseError,\n  handleServiceResult,\n  transformRequestData\n} from '@/utils';\nimport { handleRefreshToken } from './helpers';\n\nexport default class CustomAxiosInstance {\n  instance: AxiosInstance;\n\n  backendConfig: Service.BackendResultConfig;\n\n  constructor(\n    axiosConfig: AxiosRequestConfig,\n    backendConfig: Service.BackendResultConfig = {\n      codeKey: 'code',\n      dataKey: 'data',\n      msgKey: 'message',\n      successCode: 200\n    }\n  ) {\n    this.backendConfig = backendConfig;\n    this.instance = axios.create(axiosConfig);\n    this.setInterceptor();\n  }\n\n  setInterceptor() {\n    this.instance.interceptors.request.use(\n      async config => {\n        const handleConfig = { ...config };\n        if (handleConfig.headers) {\n          const contentType = handleConfig.headers['Content-Type'] as string;\n          handleConfig.data = await transformRequestData(handleConfig.data, contentType);\n          handleConfig.headers.Authorization = 'Bearer ' +localStg.get('token') || '';\n        }\n        return handleConfig;\n      },\n      (axiosError: AxiosError) => {\n        const error = handleAxiosError(axiosError);\n        return handleServiceResult(error, null);\n      }\n    );\n    this.instance.interceptors.response.use(\n      async response => {\n        const { status } = response;\n        if (status === 200 || status < 300 || status === 304) {\n          const backend = response.data;\n          const { codeKey, dataKey, successCode } = this.backendConfig;\n          if (backend[codeKey] === successCode) {\n            return handleServiceResult(null, backend[dataKey]);\n          }\n\n          if (REFRESH_TOKEN_CODE.includes(backend[codeKey])) {\n            const config = await handleRefreshToken(response.config);\n            if (config) {\n              return this.instance.request(config);\n            }\n          }\n\n          const error = handleBackendError(backend, this.backendConfig);\n          return handleServiceResult(error, null);\n        }\n        const error = handleResponseError(response);\n        return handleServiceResult(error, null);\n      },\n      (axiosError: AxiosError) => {\n        const error = handleAxiosError(axiosError);\n        return handleServiceResult(error, null);\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "src/service/request/request.ts",
    "content": "import {ref} from 'vue';\nimport type {Ref} from 'vue';\nimport type {AxiosInstance, AxiosRequestConfig} from 'axios';\nimport {useBoolean, useLoading} from '@/hooks';\nimport CustomAxiosInstance from './instance';\n\ntype RequestMethod = 'get' | 'post' | 'put' | 'delete';\n\ninterface RequestParam {\n  url: string;\n  method?: RequestMethod;\n  data?: any;\n  axiosConfig?: AxiosRequestConfig;\n}\n\nexport function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {\n  const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);\n\n  async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> {\n    const {url} = param;\n    const method = param.method || 'get';\n    const {instance} = customInstance;\n    const res = (await getRequestResponse({\n      instance,\n      method,\n      url,\n      data: param.data,\n      config: param.axiosConfig\n    })) as Service.RequestResult<T>;\n\n    return res;\n  }\n\n  function get<T>(url: string, config?: AxiosRequestConfig) {\n    return asyncRequest<T>({url, method: 'get', axiosConfig: config});\n  }\n\n  function post<T>(url: string, data?: any, config?: AxiosRequestConfig) {\n    return asyncRequest<T>({url, method: 'post', data, axiosConfig: config});\n  }\n\n  function put<T>(url: string, data?: any, config?: AxiosRequestConfig) {\n    return asyncRequest<T>({url, method: 'put', data, axiosConfig: config});\n  }\n\n  function handleDelete<T>(url: string, config: AxiosRequestConfig) {\n    return asyncRequest<T>({url, method: 'delete', axiosConfig: config});\n  }\n\n  return {\n    get,\n    post,\n    put,\n    delete: handleDelete\n  };\n}\n\ninterface RequestResultHook<T = any> {\n  data: Ref<T | null>;\n  error: Ref<Service.RequestError | null>;\n  loading: Ref<boolean>;\n  network: Ref<boolean>;\n}\n\nexport function createHookRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {\n  const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);\n\n  function useRequest<T>(param: RequestParam): RequestResultHook<T> {\n    const {loading, startLoading, endLoading} = useLoading();\n    const {bool: network, setBool: setNetwork} = useBoolean(window.navigator.onLine);\n\n    startLoading();\n    const data = ref<T | null>(null) as Ref<T | null>;\n    const error = ref<Service.RequestError | null>(null);\n\n    function handleRequestResult(response: any) {\n      const res = response as Service.RequestResult<T>;\n      data.value = res.data;\n      error.value = res.error;\n      endLoading();\n      setNetwork(window.navigator.onLine);\n    }\n\n    const {url} = param;\n    const method = param.method || 'get';\n    const {instance} = customInstance;\n\n    getRequestResponse({instance, method, url, data: param.data, config: param.axiosConfig}).then(\n      handleRequestResult\n    );\n\n    return {\n      data,\n      error,\n      loading,\n      network\n    };\n  }\n\n  function get<T>(url: string, config?: AxiosRequestConfig) {\n    return useRequest<T>({url, method: 'get', axiosConfig: config});\n  }\n\n  function post<T>(url: string, data?: any, config?: AxiosRequestConfig) {\n    return useRequest<T>({url, method: 'post', data, axiosConfig: config});\n  }\n\n  function put<T>(url: string, data?: any, config?: AxiosRequestConfig) {\n    return useRequest<T>({url, method: 'put', data, axiosConfig: config});\n  }\n\n  function handleDelete<T>(url: string, config: AxiosRequestConfig) {\n    return useRequest<T>({url, method: 'delete', axiosConfig: config});\n  }\n\n  return {\n    get,\n    post,\n    put,\n    delete: handleDelete\n  };\n}\n\nasync function getRequestResponse(params: {\n  instance: AxiosInstance;\n  method: RequestMethod;\n  url: string;\n  data?: any;\n  config?: AxiosRequestConfig;\n}) {\n  const {instance, method, url, data, config} = params;\n\n  let res: any;\n  if (method === 'get' || method === 'delete') {\n    res = await instance[method](url, config);\n  } else {\n    res = await instance[method](url, data, config);\n  }\n  return res;\n}\n"
  },
  {
    "path": "src/store/auth/helpers.ts",
    "content": "import { localStg } from '@/utils';\n\nexport function getToken() {\n  return localStg.get('token') || '';\n}\n\nexport function getUserInfo() {\n  const emptyInfo: Auth.UserInfo = {\n    userId: '',\n    userName: '',\n    userRole: 'user'\n  };\n  const userInfo: Auth.UserInfo = localStg.get('userInfo') || emptyInfo;\n\n  return userInfo;\n}\n\nexport function clearAuthStorage() {\n  localStg.remove('token');\n  localStg.remove('refreshToken');\n  localStg.remove('userInfo');\n}\n"
  },
  {
    "path": "src/store/auth/index.ts",
    "content": "import {unref, nextTick} from 'vue';\nimport {defineStore} from 'pinia';\nimport {router} from '@/router';\nimport {fetchLogin, fetchUserInfo} from '@/service';\nimport {useRouterPush} from '@/composables';\nimport {localStg} from '@/utils';\nimport {useRouteStore} from '@/store';\nimport {getToken, getUserInfo, clearAuthStorage} from './helpers';\nimport i18n from '@/plugins/vue-i18n'\n\ninterface AuthState {\n  userInfo: Auth.UserInfo;\n  token: string;\n  loginLoading: boolean;\n}\n\nexport const useAuthStore = defineStore('auth-store', {\n  state: (): AuthState => ({\n    userInfo: getUserInfo(),\n    token: getToken(),\n    loginLoading: false\n  }),\n  getters: {\n    isLogin(state) {\n      return Boolean(state.token);\n    }\n  },\n  actions: {\n    resetAuthStore() {\n      const {toLogin} = useRouterPush(false);\n      const {resetRouteStore} = useRouteStore();\n      const route = unref(router.currentRoute);\n\n      clearAuthStorage();\n      this.$reset();\n      window?.$loadingOverly?.hide()\n      window?.$dialog?.closeAll()\n\n      if (route.meta.requiresAuth) {\n        toLogin();\n      }\n      nextTick(() => {\n        resetRouteStore();\n      });\n    },\n\n    async handleActionAfterLogin(backendToken: ApiAuth.Token) {\n      const route = useRouteStore();\n      const {toLoginRedirect} = useRouterPush(false);\n\n      const loginSuccess = await this.loginByToken(backendToken);\n\n      if (loginSuccess) {\n        await route.initAuthRoute();\n\n        toLoginRedirect();\n\n        if (route.isInitAuthRoute) {\n          window.$snackBar?.success(`${i18n.global.t(\"welcomeBack\")}，${this.userInfo.userName}!`);\n        }\n\n        return;\n      }\n\n      this.resetAuthStore();\n    },\n\n    async loginByToken(backendToken: ApiAuth.Token) {\n      let successFlag = false;\n\n      const {token, refreshToken} = backendToken;\n      localStg.set('token', token);\n      localStg.set('refreshToken', refreshToken);\n\n      const {data} = await fetchUserInfo();\n      if (data) {\n        localStg.set('userInfo', data);\n\n        this.userInfo = data;\n        this.token = token;\n\n        successFlag = true;\n      }\n\n      return successFlag;\n    },\n\n    async login(userName: string, password: string) {\n      this.loginLoading = true;\n      const {data} = await fetchLogin(userName, password);\n      if (data) {\n        await this.handleActionAfterLogin(data);\n      }\n      this.loginLoading = false;\n    },\n\n    async updateUserRole(userRole: Auth.RoleType) {\n      const {resetRouteStore, initAuthRoute} = useRouteStore();\n\n      const accounts: Record<Auth.RoleType, { userName: string; password: string }> = {\n        admin: {\n          userName: 'Admin',\n          password: 'admin123'\n        },\n        user: {\n          userName: 'User01',\n          password: 'user01123'\n        }\n      };\n      const {userName, password} = accounts[userRole];\n      const {data} = await fetchLogin(userName, password);\n      if (data) {\n        await this.loginByToken(data);\n        resetRouteStore();\n        await initAuthRoute();\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "src/store/flow/index.ts",
    "content": "import {defineStore} from 'pinia'\nimport {Base} from 'diagram-js/lib/model'\nimport Canvas from 'diagram-js/lib/core/Canvas'\nimport ElementRegistry from 'diagram-js/lib/core/ElementRegistry'\n\ntype ModelerStore = {\n  activeElement: any\n  activeElementId: any\n  modeler: any\n  moddle: any\n  modeling: any\n  commandStack: any,\n  canvas: Canvas | null\n  elementRegistry: ElementRegistry | null\n}\n\nconst defaultState: ModelerStore = {\n  activeElement: undefined,\n  activeElementId: undefined,\n  modeler: null,\n  moddle: null,\n  modeling: null,\n  canvas: null,\n  elementRegistry: null,\n  commandStack: null,\n}\n\nexport const useModelStore = defineStore('modeler', {\n  state: () => defaultState,\n  getters: {\n    getActive: (state) => state.activeElement,\n    getActiveId: (state) => state.activeElementId,\n    getModeler: (state) => state.modeler,\n    getModdle: (state) => state.moddle,\n    getModeling: (state) => state.modeling,\n    getCommandStack: (state) => state.commandStack,\n    getCanvas: (state) => state.canvas,\n    getElRegistry: (state) => state.elementRegistry\n  },\n  actions: {\n    setModeler(modeler: any) {\n      this.modeler = modeler\n    },\n    setModules<K extends keyof ModelerStore>(key: K, module: any) {\n      this[key] = module\n    },\n    setElement(element: Base, id: string) {\n      this.activeElement = element\n      this.activeElementId = id\n    }\n  }\n})\n"
  },
  {
    "path": "src/store/index.ts",
    "content": "import {createPinia} from 'pinia'\nimport type {App} from \"vue\";\n\nexport function setupStore(app: App) {\n  const store = createPinia()\n  app.use(store)\n}\n\nexport * from './theme'\nexport * from './route'\nexport * from './auth'\nexport * from './subscribe'\nexport * from './flow'\n"
  },
  {
    "path": "src/store/route/index.ts",
    "content": "import {defineStore} from 'pinia';\nimport {ROOT_ROUTE, constantRoutes, router, routes as staticRoutes} from '@/router';\nimport {fetchUserRoutes} from '@/service';\nimport {\n  localStg,\n  filterAuthRoutesByUserPermission,\n  getCacheRoutes,\n  getConstantRouteNames,\n  transformAuthRouteToVueRoutes,\n  transformAuthRouteToVueRoute,\n  transformAuthRouteToMenu,\n  transformAuthRouteToSearchMenus,\n  transformRouteNameToRoutePath,\n  transformRoutePathToRouteName,\n  sortRoutes\n} from '@/utils';\nimport {useAuthStore} from '../auth';\n\ninterface RouteState {\n  authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];\n  isInitAuthRoute: boolean;\n  routeHomeName: AuthRoute.AllRouteKey;\n  menus: App.GlobalMenuOption[];\n  searchMenus: AuthRoute.Route[];\n  cacheRoutes: string[];\n}\n\nexport const useRouteStore = defineStore('route-store', {\n  state: (): RouteState => ({\n    authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,\n    isInitAuthRoute: false,\n    routeHomeName: transformRoutePathToRouteName(import.meta.env.VITE_ROUTE_HOME_PATH),\n    menus: [],\n    searchMenus: [],\n    cacheRoutes: []\n  }),\n  actions: {\n    resetRouteStore() {\n      this.resetRoutes();\n      this.$reset();\n    },\n    resetRoutes() {\n      const routes = router.getRoutes();\n      routes.forEach(route => {\n        const name = (route.name || 'root') as AuthRoute.AllRouteKey;\n        if (!this.isConstantRoute(name)) {\n          router.removeRoute(name);\n        }\n      });\n    },\n    isConstantRoute(name: AuthRoute.AllRouteKey) {\n      const constantRouteNames = getConstantRouteNames(constantRoutes);\n      return constantRouteNames.includes(name);\n    },\n    isValidConstantRoute(name: AuthRoute.AllRouteKey) {\n      const NOT_FOUND_PAGE_NAME: AuthRoute.NotFoundRouteKey = 'not-found';\n      const constantRouteNames = getConstantRouteNames(constantRoutes);\n      return constantRouteNames.includes(name) && name !== NOT_FOUND_PAGE_NAME;\n    },\n    handleAuthRoute(routes: AuthRoute.Route[]) {\n      (this.menus as App.GlobalMenuOption[]) = transformAuthRouteToMenu(routes);\n      this.searchMenus = transformAuthRouteToSearchMenus(routes);\n\n      const vueRoutes = transformAuthRouteToVueRoutes(routes);\n\n      vueRoutes.forEach(route => {\n        router.addRoute(route);\n      });\n\n      this.cacheRoutes = getCacheRoutes(vueRoutes);\n    },\n    handleUpdateRootRedirect(routeKey: AuthRoute.AllRouteKey) {\n      if (routeKey === 'root' || routeKey === 'not-found') {\n        throw new Error('root or not-found should not be routeKey');\n      }\n      const rootRoute: AuthRoute.Route = {...ROOT_ROUTE, redirect: transformRouteNameToRoutePath(routeKey)};\n      const rootRouteName: AuthRoute.AllRouteKey = 'root';\n      router.removeRoute(rootRouteName);\n      const rootVueRoute = transformAuthRouteToVueRoute(rootRoute)[0];\n      router.addRoute(rootVueRoute);\n    },\n    async initDynamicRoute() {\n      const {userId} = localStg.get('userInfo') || {};\n\n      if (!userId) {\n        throw new Error('userId is mandatory ');\n      }\n\n      const {error, data} = await fetchUserRoutes(userId);\n\n      if (!error) {\n        this.routeHomeName = data.home;\n        this.handleUpdateRootRedirect(data.home);\n        this.handleAuthRoute(sortRoutes(data.routes));\n\n        this.isInitAuthRoute = true;\n      }\n    },\n    async initStaticRoute() {\n      const auth = useAuthStore();\n\n      const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);\n      this.handleAuthRoute(routes);\n\n      this.isInitAuthRoute = true;\n    },\n    async initAuthRoute() {\n      if (this.authRouteMode === 'dynamic') {\n        await this.initDynamicRoute();\n      } else {\n        await this.initStaticRoute();\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "src/store/subscribe/index.ts",
    "content": "import subscribeThemeStore from './theme';\n\nexport function subscribeStore() {\n  subscribeThemeStore();\n}\n"
  },
  {
    "path": "src/store/subscribe/theme.ts",
    "content": "import {effectScope, onScopeDispose, watch} from 'vue';\nimport {useOsTheme} from 'vooks';\nimport {useThemeStore} from '@/store';\n\nexport default function subscribeThemeStore() {\n  const theme = useThemeStore();\n  const osTheme = useOsTheme();\n  const scope = effectScope();\n  const {themes, global} = useTheme()\n\n  scope.run(() => {\n    // themeconfig\n    watch(\n      () => theme,\n      (n) => {\n        themes.value[\"dark\"].colors.primary = n.primary\n        themes.value[\"light\"].colors.primary = n.primary\n        global.name.value = n.globalTheme\n        theme.cacheThemeSettings()\n      },\n      {immediate: true, deep: true}\n    );\n\n    // watch os theme\n    watch(\n      osTheme,\n      newValue => {\n        if (theme.followOs) {\n          global.name.value = newValue || 'light'\n          theme.cacheThemeSettings()\n        }\n      },\n      {immediate: true}\n    );\n  });\n\n  onScopeDispose(() => {\n    scope.stop();\n  });\n}\n"
  },
  {
    "path": "src/store/theme/helpers.ts",
    "content": "import {localStg} from '@/utils';\nimport configs from \"@/configs\";\n\nexport function initThemeSettings() {\n  const storageSettings = localStg.get('themeSettings');\n  if (storageSettings) {\n    return storageSettings;\n  }\n\n  return configs.theme;\n}\n"
  },
  {
    "path": "src/store/theme/index.ts",
    "content": "import {defineStore} from 'pinia'\nimport {localStg} from \"@/utils\";\nimport {initThemeSettings} from \"@/store/theme/helpers\";\n\nexport const useThemeStore = defineStore(\"theme\", {\n  state: () => {\n    return {\n      ...initThemeSettings(),\n    }\n  },\n  actions: {\n    cacheThemeSettings() {\n      localStg.set('themeSettings', this.$state);\n    },\n  }\n})\n"
  },
  {
    "path": "src/translations/en.ts",
    "content": "export default {\n  'welcomeBack': 'welcome back',\n  common: {\n    add: 'Add',\n    cancel: 'Cancel',\n    confirm: 'confirm',\n    description: 'Description',\n    delete: 'Delete',\n    title: 'Title',\n    save: 'Save',\n    faq: 'FAQ',\n    contact: 'Contact Us',\n    tos: 'Terms of Service',\n    policy: 'Privacy Policy'\n  },\n  board: {\n    titlePlaceholder: 'Enter a title for this card',\n    deleteDescription: 'Are you sure you want to delete this card?',\n    editCard: 'Edit Card',\n    deleteCard: 'Delete Card',\n    state: {\n      TODO: 'TO DO',\n      INPROGRESS: 'INPROGRESS',\n      TESTING: 'TESTING',\n      DONE: 'DONE'\n    }\n  },\n  chat: {\n    online: 'Users Online ({count})',\n    addChannel: 'Add Channel',\n    channel: 'Channel | Channels',\n    message: 'Message'\n  },\n  email: {\n    compose: 'Compose Email',\n    send: 'Send',\n    subject: 'Subject',\n    labels: 'Labels',\n    emptyList: 'Empty email list',\n    inbox: 'Inbox',\n    sent: 'Sent',\n    drafts: 'Drafts',\n    starred: 'Starred',\n    trash: 'Trash',\n    work: 'Work',\n    invoice: 'Invoice'\n  },\n  todo: {\n    addTask: 'Add Task',\n    tasks: 'Tasks',\n    completed: 'Completed',\n    labels: 'Labels'\n  },\n  dashboard: {\n    activity: 'Activity',\n    weeklySales: 'Weekly Sales',\n    sales: 'Sales',\n    recentOrders: 'Recent Orders',\n    sources: 'Traffic Sources',\n    lastweek: 'vs last week',\n    orders: 'Orders',\n    customers: 'Customers',\n    tickets: 'Support Tickets',\n    viewReport: 'View Report'\n  },\n  usermenu: {\n    profile: 'Profile',\n    signin: 'Sign In',\n    dashboard: 'Dashboard',\n    signout: 'Sign Out',\n    notSignin: 'Not Sign in',\n  },\n  error: {\n    notfound: 'Page Not Found',\n    other: 'An Error Ocurred'\n  },\n  check: {\n    title: 'Set New Password',\n    backtosign: 'Back to Sign In',\n    newpassword: 'New Password',\n    button: 'Set new password and Sign in',\n    error: 'The action link is invalid',\n    verifylink: 'Verifying link...',\n    verifyemail: 'Verifying email address...',\n    emailverified: 'Email verified! Redirecting...'\n  },\n  forgot: {\n    title: 'Forgot Password?',\n    subtitle: 'Enter your account email address and we will send you a link to reset your password.',\n    email: 'Email',\n    button: 'Request Password Reset',\n    backtosign: 'Back to Sign In'\n  },\n  login: {\n    title: 'Sign In',\n    email: 'Email',\n    password: 'Password',\n    button: 'Sign In',\n    orsign: 'Or sign in with',\n    forgot: 'Forgot password?',\n    noaccount: 'Don\\'t have an account?',\n    create: 'Create one here',\n    error: 'The email / password combination is invalid'\n  },\n  register: {\n    title: 'Create Account',\n    name: 'Full name',\n    email: 'Email',\n    password: 'Password',\n    button: 'Create Account',\n    orsign: 'Or sign up with',\n    agree: 'By signing up, you agree to the',\n    account: 'Already have an account?',\n    signin: 'Sign In'\n  },\n  utility: {\n    maintenance: 'In Maintenance'\n  },\n  faq: {\n    call: 'Have other questions? Please reach out '\n  },\n  ecommerce: {\n    products: 'Products',\n    filters: 'Filters',\n    collections: 'Collections',\n    priceRange: 'Price Range',\n    customerReviews: 'Customer Reviews',\n    up: 'and up',\n    brand: 'Brand',\n    search: 'Search for product',\n    results: 'Results ( {0} of {1} )',\n    orders: 'Orders',\n    shipping: 'Shipping',\n    freeShipping: 'Free Shipping',\n    inStock: 'In Stock',\n    quantity: 'Quantity',\n    addToCart: 'Add To Cart',\n    buyNow: 'Buy Now',\n    price: 'Price',\n    about: 'About this item',\n    description: 'Description',\n    reviews: 'Reviews',\n    details: 'Product Details',\n    cart: 'Cart',\n    summary: 'Order Summary',\n    total: 'Total',\n    discount: 'Discount',\n    subtotal: 'Subtotal',\n    continue: 'Continue Shopping',\n    checkout: 'Checkout'\n  },\n  menu: {\n    apps: 'apps',\n    search: 'Search (press \"ctrl + /\" to focus)',\n    dashboard: 'Dashboard',\n    logout: 'Logout',\n    profile: 'Profile',\n    blank: 'Blank Page',\n    pages: 'Pages',\n    others: 'Others',\n    email: 'Email',\n    chat: 'Chat',\n    'chat-channel': 'Chat Channel',\n    todo: 'To Do',\n    board: 'Task Board',\n    users: 'Users',\n    usersList: 'List',\n    usersEdit: 'Edit',\n    ecommerce: 'Ecommerce',\n    ecommerceList: 'Products',\n    ecommerceProductDetails: 'Product Details',\n    ecommerceOrders: 'Orders',\n    ecommerceCart: 'Cart',\n    auth: 'Auth Pages',\n    authLogin: 'Signin / Login',\n    authRegister: 'Signup / Register',\n    authVerify: 'Verify Email',\n    authForgot: 'Forgot Password',\n    authReset: 'Reset Password',\n    errorPages: 'Error Pages',\n    errorNotFound: 'Not Found / 404',\n    errorUnexpected: 'Unexpected / 500',\n    utilityPages: 'Utility Pages',\n    utilityMaintenance: 'Maintenance',\n    utilitySoon: 'Coming Soon',\n    utilityHelp: 'FAQs / Help',\n    levels: 'Menu Levels',\n    'levels2-1': 'levels2-1',\n    'levels2-2': 'levels2-2',\n    'levels3-1': 'levels3-1',\n    'levels3-2': 'levels3-2',\n    disabled: 'Menu Disabled',\n    docs: 'Documentation',\n    feedback: 'Feedback',\n    support: 'Support',\n    landingPage: 'Landing Page',\n    pricingPage: 'Pricing Page',\n    'flowable': 'Flowable',\n    'flowable-design': 'Flowable Design',\n    'form-list': 'Forms',\n    'form-design': 'Form Design'\n  },\n  // Vuetify components translations\n  $vuetify: {\n    badge: 'Badge',\n    close: 'Close',\n    dataIterator: {\n      noResultsText: 'No matching records found',\n      loadingText: 'Loading items...'\n    },\n    dataTable: {\n      itemsPerPageText: 'Rows per page:',\n      ariaLabel: {\n        sortDescending: 'Sorted descending.',\n        sortAscending: 'Sorted ascending.',\n        sortNone: 'Not sorted.',\n        activateNone: 'Activate to remove sorting.',\n        activateDescending: 'Activate to sort descending.',\n        activateAscending: 'Activate to sort ascending.'\n      },\n      sortBy: 'Sort by'\n    },\n    dataFooter: {\n      itemsPerPageText: 'Items per page:',\n      itemsPerPageAll: 'All',\n      nextPage: 'Next page',\n      prevPage: 'Previous page',\n      firstPage: 'First page',\n      lastPage: 'Last page',\n      pageText: '{0}-{1} of {2}'\n    },\n    datePicker: {\n      itemsSelected: '{0} selected',\n      nextMonthAriaLabel: 'Next month',\n      nextYearAriaLabel: 'Next year',\n      prevMonthAriaLabel: 'Previous month',\n      prevYearAriaLabel: 'Previous year'\n    },\n    noDataText: 'No data available',\n    carousel: {\n      prev: 'Previous visual',\n      next: 'Next visual',\n      ariaLabel: {\n        delimiter: 'Carousel slide {0} of {1}'\n      }\n    },\n    calendar: {\n      moreEvents: '{0} more'\n    },\n    fileInput: {\n      counter: '{0} files',\n      counterSize: '{0} files ({1} in total)'\n    },\n    timePicker: {\n      am: 'AM',\n      pm: 'PM'\n    },\n    pagination: {\n      ariaLabel: {\n        wrapper: 'Pagination Navigation',\n        next: 'Next page',\n        previous: 'Previous page',\n        page: 'Goto Page {0}',\n        currentPage: 'Current Page, Page {0}'\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/translations/zh.ts",
    "content": "export default {\n  'welcomeBack': '欢迎回来',\n  'common': {\n    'add': '加',\n    'confirm': '确认',\n    'cancel': '取消',\n    'description': '描述',\n    'delete': '删除',\n    'title': '标题',\n    'save': '保存',\n    'faq': '常问问题',\n    'contact': '联系我们',\n    'tos': '服务条款',\n    'policy': '隐私政策'\n  },\n  'board': {\n    'titlePlaceholder': '输入此卡的标题',\n    'deleteDescription': '您确定要删除此卡吗？',\n    'editCard': '编辑卡',\n    'deleteCard': '删除卡',\n    'state': {\n      'TODO': '去做',\n      'INPROGRESS': '进行中',\n      'TESTING': '测试中',\n      'DONE': '完成'\n    }\n  },\n  'chat': {\n    'online': '在线用户（{count}）',\n    'addChannel': '添加频道',\n    'channel': '频道|频道',\n    'message': '信息'\n  },\n  'email': {\n    'compose': '编写邮件',\n    'send': '发送',\n    'subject': '学科',\n    'labels': '标签',\n    'emptyList': '空的电子邮件清单',\n    'inbox': '收件箱',\n    'sent': '已发送',\n    'drafts': '草稿',\n    'starred': '已加星标',\n    'trash': '垃圾',\n    'work': '工作',\n    'invoice': '发票'\n  },\n  'todo': {\n    'addTask': '添加任务',\n    'tasks': '任务',\n    'completed': '已完成',\n    'labels': '标签'\n  },\n  'dashboard': {\n    'activity': '活动',\n    'weeklySales': '每周销售',\n    'sales': '营业额',\n    'recentOrders': '最近的订单',\n    'sources': '流量来源',\n    'lastweek': '与上周',\n    'orders': '订单',\n    'customers': '顾客',\n    'tickets': '支持票',\n    'viewReport': '查看报告'\n  },\n  'usermenu': {\n    'profile': '个人资料',\n    'signin': '登入',\n    'dashboard': '仪表板',\n    'signout': '登出',\n    'notSignin': '未登录',\n  },\n  'error': {\n    'notfound': '网页未找到',\n    'other': '发生错误'\n  },\n  'check': {\n    'title': '设置新密码',\n    'backtosign': '返回登录',\n    'newpassword': '新密码',\n    'button': '设置新密码并登录',\n    'error': '动作链接无效',\n    'verifylink': '正在验证链接...',\n    'verifyemail': '正在验证电子邮件地址...',\n    'emailverified': '电子邮件已验证！重定向中...'\n  },\n  'forgot': {\n    'title': '忘记密码？',\n    'subtitle': '输入您的帐户电子邮件地址，我们将向您发送一个链接以重置密码。',\n    'email': '电子邮件',\n    'button': '要求重设密码',\n    'backtosign': '返回登录'\n  },\n  'login': {\n    'title': '登入',\n    'email': '电子邮件',\n    'password': '密码',\n    'button': '登入',\n    'orsign': '或使用登录',\n    'forgot': '忘记密码？',\n    'noaccount': '还没有帐号？',\n    'create': '在此处创建一个',\n    'error': '电子邮件/密码组合无效'\n  },\n  'register': {\n    'title': '创建帐号',\n    'name': '全名',\n    'email': '电子邮件',\n    'password': '密码',\n    'button': '创建帐号',\n    'orsign': '或注册',\n    'agree': '签署即表示您同意',\n    'account': '已经有帐号了？',\n    'signin': '登入'\n  },\n  'utility': {\n    'maintenance': '维护中'\n  },\n  'faq': {\n    'call': '还有其他问题吗？请伸出手'\n  },\n  'ecommerce': {\n    'products': '产品展示',\n    'filters': '筛选器',\n    'collections': '馆藏',\n    'priceRange': '价格范围',\n    'customerReviews': '顾客评论',\n    'up': '及以上',\n    'brand': '牌',\n    'search': '搜索产品',\n    'results': '结果（{0}，共{1}）',\n    'orders': '订单',\n    'shipping': '运输',\n    'freeShipping': '免费送货',\n    'inStock': '有现货',\n    'quantity': '数量',\n    'addToCart': '添加到购物车',\n    'buyNow': '立即购买',\n    'price': '价钱',\n    'about': '关于这个项目',\n    'description': '描述',\n    'reviews': '评论',\n    'details': '产品详情',\n    'cart': '大车',\n    'summary': '订单摘要',\n    'total': '总',\n    'discount': '折扣',\n    'subtotal': '小计',\n    'continue': '继续购物',\n    'checkout': '查看'\n  },\n  'menu': {\n    'apps': 'apps',\n    'search': '搜索（按“ Ctrl + /”进行聚焦）',\n    'dashboard': '仪表板',\n    'logout': '登出',\n    'profile': '个人资料',\n    'blank': '空白页',\n    'pages': '页数',\n    'others': '其他',\n    'email': '电子邮件',\n    'chat': '聊天室',\n    'chat-channel': '聊天频道',\n    'todo': '去做',\n    'board': '任务板',\n    'users': '用户数',\n    'usersList': '清单',\n    'usersEdit': '编辑',\n    'ecommerce': '电子商务',\n    'ecommerceList': '产品展示',\n    'ecommerceProductDetails': '产品详情',\n    'ecommerceOrders': '订单',\n    'ecommerceCart': '大车',\n    'auth': '验证页面',\n    'authLogin': '登录/登录',\n    'authRegister': '注册/注册',\n    'authVerify': '验证邮件',\n    'authForgot': '忘记密码',\n    'authReset': '重设密码',\n    'errorPages': '错误页面',\n    'errorNotFound': '找不到/ 404',\n    'errorUnexpected': '意想不到的/ 500',\n    'utilityPages': '实用页面',\n    'utilityMaintenance': '保养',\n    'utilitySoon': '快来了',\n    'utilityHelp': '常见问题/帮助',\n    'levels': '菜单级别',\n    'levels2-1': '级别2.1',\n    'levels2-2': '级别2.2',\n    'levels3-1': '级别3.1',\n    'levels3-2': '级别3.2',\n    'disabled': '菜单已禁用',\n    'docs': '文献资料',\n    'feedback': '反馈',\n    'support': '支持',\n    'landingPage': '登陆页面',\n    'pricingPage': '定价页面',\n    'flowable': '流程管理',\n    'flowable-design': '流程设计',\n    'form-list':'表单',\n    'form-design':'表单设计'\n  },\n  '$vuetify': {\n    'badge': '徽章',\n    'close': '关',\n    'dataIterator': {\n      'noResultsText': '未找到匹配的记录',\n      'loadingText': '正在载入项目...'\n    },\n    'dataTable': {\n      'itemsPerPageText': '每页行数：',\n      'ariaLabel': {\n        'sortDescending': '降序排列。',\n        'sortAscending': '升序排列。',\n        'sortNone': '未排序。',\n        'activateNone': '激活以删除排序。',\n        'activateDescending': '激活以降序排列。',\n        'activateAscending': '激活以升序排序。'\n      },\n      'sortBy': '排序方式'\n    },\n    'dataFooter': {\n      'itemsPerPageText': '每页项目：',\n      'itemsPerPageAll': '所有',\n      'nextPage': '下一页',\n      'prevPage': '上一页',\n      'firstPage': '第一页',\n      'lastPage': '最后一页',\n      'pageText': '{2}中的{0}-{1}'\n    },\n    'datePicker': {\n      'itemsSelected': '已选择{0}',\n      'nextMonthAriaLabel': '下个月',\n      'nextYearAriaLabel': '明年',\n      'prevMonthAriaLabel': '前一个月',\n      'prevYearAriaLabel': '前一年'\n    },\n    'noDataText': '无可用数据',\n    'carousel': {\n      'prev': '以前的视觉',\n      'next': '下一个视觉',\n      'ariaLabel': {\n        'delimiter': '{1}的轮播幻灯片{0}'\n      }\n    },\n    'calendar': {\n      'moreEvents': '还有{0}个'\n    },\n    'fileInput': {\n      'counter': '{0}个文件',\n      'counterSize': '{0}个文件（共{1}个）'\n    },\n    'timePicker': {\n      'am': 'AM',\n      'pm': 'PM'\n    },\n    'pagination': {\n      'ariaLabel': {\n        'wrapper': '分页导航',\n        'next': '下一页',\n        'previous': '上一页',\n        'page': '转到页面{0}',\n        'currentPage': '当前页，第{0}页'\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/typings/api.d.ts",
    "content": "declare namespace ApiCommon {\n  interface PageResult<T = any> {\n    pageNo: number,\n    pageSize: number,\n    list: T,\n    total: number,\n  }\n}\n\ndeclare namespace ApiAuth {\n  interface Token {\n    token: string;\n    refreshToken: string;\n  }\n\n  type UserInfo = Auth.UserInfo;\n}\n\ndeclare namespace ApiRoute {\n  interface Route {\n    routes: AuthRoute.Route[];\n    home: AuthRoute.AllRouteKey;\n  }\n}\n\ndeclare namespace ApiForm {\n  interface Form {\n    config: string;\n    id: string;\n    name: string;\n    status: '1' | '0';\n    created: string;\n  }\n}\n\ndeclare namespace ApiUserManagement {\n\n  interface Address {\n    city?: string,\n    state?: string,\n    country?: string,\n    zipCode?: string,\n    detail?: string,\n  }\n\n  interface User {\n    id: string,\n    name: string,\n    verified: boolean,\n    created: string,\n    lastSignIn: string,\n    birthDay: string,\n    gender: '0' | '1' | '2';\n    phone: string;\n    email: string;\n    userStatus: '1' | '2' | '4';\n    avatar: string\n    address: Address\n  }\n}\n\ndeclare namespace ApiChatManagement {\n  interface message {\n    id: string,\n    text: string,\n    timestamp: string,\n    image?: string,\n    user: {\n      avatar: string,\n      id: string\n    }\n  }\n}\n\ndeclare namespace ApiChatManagement {\n  interface message {\n    id: string,\n    text: string,\n    timestamp: string,\n    image?: string,\n    user: {\n      avatar: string,\n      id: string\n    }\n  }\n}\n\ndeclare namespace ApiChatManagement {\n  interface message {\n    id: string,\n    text: string,\n    timestamp: string,\n    image?: string,\n    user: {\n      avatar: string,\n      id: string\n    }\n  }\n}\n"
  },
  {
    "path": "src/typings/business.d.ts",
    "content": "declare namespace Auth {\n  type RoleType = keyof typeof import('@/enum').EnumUserRole;\n\n  interface UserInfo {\n    userId: string;\n    userName: string;\n    userRole: RoleType;\n    userAvatar?: string;\n  }\n}\n\ndeclare namespace UserManagement {\n  interface User extends ApiUserManagement.User {\n    role: Auth.RoleType\n  }\n\n  /**\n   * 用户性别\n   * - 0: 女\n   * - 1: 男\n   */\n  type GenderKey = NonNullable<User['gender']>;\n\n  /**\n   * 用户状态\n   * - 1: 启用\n   * - 2: 禁用\n   * - 3: 冻结\n   * - 4: 软删除\n   */\n  type UserStatusKey = NonNullable<User['userStatus']>;\n}\n\ndeclare namespace FormManagement {\n  interface Form extends ApiForm.Form {\n\n  }\n  type FormStatusKey = NonNullable<Form['status']>;\n}\n"
  },
  {
    "path": "src/typings/camunda.d.ts",
    "content": "declare module \"bpmn-js/*\"\ndeclare module \"diagram-js/*\"\ndeclare module \"@bpmn-io/*\"\ndeclare module \"bpmn-js-properties-panel\"\ndeclare module \"camunda-bpmn-moddle/*\"\ndeclare module \"*.bpmn\" {\n  const value: any; // Add better type definitions here if desired.\n  export default value;\n}\n"
  },
  {
    "path": "src/typings/config.d.ts",
    "content": "interface Config {\n  theme: ThemeConfig.Config,\n  locales: any\n  currency: CurrencyConfig.Config,\n}\n\ndeclare namespace CurrencyConfig {\n  interface Currency {\n    label: string,\n    decimalDigits: number,\n    decimalSeparator: string\n    thousandsSeparator: string,\n    currencySymbol: string,\n    currencySymbolNumberOfSpaces: number,\n    currencySymbolPosition: string\n  }\n\n  interface Config {\n    currency: Currency,\n    availableCurrencies: Currency[],\n  }\n}\n\ndeclare namespace ThemeConfig {\n  interface Config {\n\n    //primary color\n    primary: string,\n\n    //follow OS theme\n    followOs: boolean,\n\n    // global theme for the theme\n    globalTheme: string,\n\n    // side menu theme, use global theme or custom\n    menuTheme: string,\n\n    // toolbar theme, use global theme or custom\n    toolbarTheme: string,\n\n    // show toolbar detached from top\n    isToolbarDetached: boolean,\n\n    // wrap pages content with a max-width\n    isContentBoxed: boolean,\n\n    // application is right to left\n    isRTL: boolean,\n\n    // dark theme colors\n    dark: import('vuetify').ThemeDefinition,\n\n    // light theme colors\n    light: import('vuetify').ThemeDefinition,\n  }\n}\n\ndeclare namespace NavigationConfig {\n  interface Menu {\n    icon?: string\n    key?: string,\n    text?: string,\n    link?: string,\n    regex?: RegExp,\n    disabled?: boolean,\n    items?: NonNullable<Menu[]>\n  }\n\n  interface Config {\n    menu: Menu[]\n    footer: Footer[]\n  }\n\n  interface Footer {\n    text?: string,\n    key: string,\n    href?: string,\n    target?: string\n  }\n}\n"
  },
  {
    "path": "src/typings/env.d.ts",
    "content": "type ServiceEnvType = 'dev' | 'test' | 'prod';\n\ninterface ServiceEnvConfig {\n  url: string;\n  urlPattern: '/url-pattern';\n  secondUrl: string;\n  secondUrlPattern: '/second-url-pattern';\n}\n\ninterface ImportMetaEnv {\n  readonly VITE_BASE_URL: string;\n  readonly VITE_APP_NAME: string;\n  readonly VITE_APP_TITLE: string;\n  readonly VITE_APP_DESC: string;\n  readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';\n  readonly VITE_ROUTE_HOME_PATH: AuthRoute.RoutePath;\n  readonly VITE_ICON_PREFFIX: string;\n  readonly VITE_ICON_LOCAL_PREFFIX: string;\n  readonly VITE_SERVICE_ENV?: ServiceEnvType;\n  readonly VITE_HTTP_PROXY?: 'Y' | 'N';\n  readonly VITE_VISUALIZER?: 'Y' | 'N';\n  readonly VITE_COMPRESS?: 'Y' | 'N';\n  readonly VITE_COMPRESS_TYPE?: 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw';\n  readonly VITE_PWA?: 'Y' | 'N';\n  readonly VITE_HASH_ROUTE?: 'Y' | 'N';\n  readonly VITE_VERCEL?: 'Y' | 'N';\n}\n\ninterface ImportMeta {\n  readonly env: ImportMetaEnv;\n}\n"
  },
  {
    "path": "src/typings/filter.d.ts",
    "content": "declare namespace filters {\n  type formatCurrency = (value: number, currency?: CurrencyConfig.Currency) => number | string;\n}\n\ninterface filtersConfig {\n  formatCurrency: filters.formatCurrency\n}\n\ndeclare module '@vue/runtime-core' {\n  export interface ComponentCustomProperties {\n    $filters: filtersConfig\n  }\n}\n\nexport {}\n"
  },
  {
    "path": "src/typings/global.d.ts",
    "content": "interface Window {\n  $snackBar?: import('@/components/provider').SnackBarApiInjection,\n  $loadingOverly?: import('@/components/provider').LoadingOverlyApiInjection,\n  $dialog?: import(\"@/components/provider\").DialogApiInjection\n}\n\ndeclare namespace Common {\n  type StrategyAction = [boolean, () => void];\n}\n\ndeclare const PROJECT_BUILD_TIME: string;\n"
  },
  {
    "path": "src/typings/page-route.d.ts",
    "content": "declare namespace PageRoute {\n  /**\n   * the root route key\n   * @translate 根路由\n   */\n  type RootRouteKey = 'root';\n\n  /**\n   * the not found route, which catch the invalid route path\n   * @translate 未找到路由(捕获无效路径的路由)\n   */\n  type NotFoundRouteKey = 'not-found';\n\n  /**\n   * the route key\n   * @translate 页面路由\n   */\n  type RouteKey =\n    | '403'\n    | '404'\n    | '500'\n    | 'login'\n    | 'not-found'\n    | 'dashboard'\n    | 'dashboard_analytics'\n    | 'apps'\n    | 'apps_manager-user'\n    | 'apps_manager-user_list'\n    | 'apps_manager-user_edit'\n    | 'apps_board'\n    | 'apps_todo'\n    | 'apps_todo_tasks'\n    | 'apps_todo_completed'\n    | 'apps_todo_label'\n    | 'apps_chat'\n    | 'apps_chat-channel'\n    | 'pages'\n    | 'pages_error'\n    | 'pages_error_notfound'\n    | 'pages_error_unexpected'\n    | 'other'\n    | 'blank-page'\n    | 'other_menu-levels'\n    | 'other_menu-levels-2-1'\n    | 'other_menu-levels-2-2'\n    | 'other_menu-levels-3-1'\n    | 'other_menu-levels-3-2'\n    | 'flowable'\n    | 'flowable_design'\n    | 'form'\n    | 'form_list'\n    | 'form_design'\n    ;\n  /**\n   * last degree route key, which has the page file\n   * @translate 最后一级路由(该级路有对应的页面文件)\n   */\n  type LastDegreeRouteKey = Extract<RouteKey,\n    | '403'\n    | '404'\n    | '500'\n    | 'login'\n    | 'not-found'\n    | 'dashboard_analytics'\n    | 'apps_manager-user_list'\n    | 'apps_manager-user_edit'\n    | 'apps_board'\n    | 'apps_todo_tasks'\n    | 'apps_todo_completed'\n    | 'apps_todo_label'\n    | 'apps_chat-channel'\n    | 'pages_error_notfound'\n    | 'pages_error_unexpected'\n    | 'other_menu-levels-2-1'\n    | 'other_menu-levels-3-1'\n    | 'other_menu-levels-3-2'\n    | 'flowable_design'\n    | 'form_list'\n    | 'form_design'\n    >;\n}\n"
  },
  {
    "path": "src/typings/route.d.ts",
    "content": "declare namespace AuthRoute {\n  type RootRoutePath = '/';\n\n  type NotFoundRoutePath = '/:pathMatch(.*)*';\n\n  type RootRouteKey = PageRoute.RootRouteKey;\n\n  type NotFoundRouteKey = PageRoute.NotFoundRouteKey;\n\n  type RouteKey = PageRoute.RouteKey;\n\n  type LastDegreeRouteKey = PageRoute.LastDegreeRouteKey;\n\n  type AllRouteKey = RouteKey | RootRouteKey | NotFoundRouteKey;\n\n  type RoutePath<K extends AllRouteKey = AllRouteKey> = AuthRouteUtils.GetRoutePath<K>;\n\n  type RouteComponentType = 'basic' | 'blank' | 'self' | 'auth' | 'error' | 'todo' | 'chat';\n\n  interface RouteMeta<K extends AuthRoute.RoutePath> {\n    title: string;\n    dynamicPath?: AuthRouteUtils.GetDynamicPath<K>;\n    singleLayout?: Extract<RouteComponentType, 'basic' | 'blank' | 'auth' | 'error' | 'todo' | 'chat'>;\n    requiresAuth?: boolean;\n    permissions?: Auth.RoleType[];\n    keepAlive?: boolean;\n    icon?: string;\n    hide?: boolean;\n    href?: string;\n    multiTab?: boolean;\n    order?: number;\n    activeMenu?: RouteKey;\n    multi?: boolean;\n    affix?: boolean;\n  }\n\n  type Route<K extends AllRouteKey = AllRouteKey> = K extends AllRouteKey\n    ? {\n    name: K;\n    path: AuthRouteUtils.GetRoutePath<K>;\n    redirect?: AuthRouteUtils.GetRoutePath;\n    /**\n     * 路由组件\n     * - basic: 基础布局，具有公共部分的布局\n     * - blank: 空白布局\n     * - multi: 多级路由布局(三级路由或三级以上时，除第一级路由和最后一级路由，其余的采用该布局)\n     * - self: 作为子路由，使用自身的布局(作为最后一级路由，没有子路由)\n     */\n    component?: RouteComponentType;\n    children?: Route[];\n    meta: RouteMeta<AuthRouteUtils.GetRoutePath<K>>;\n  } & Omit<import('vue-router').RouteRecordRaw, 'name' | 'path' | 'redirect' | 'component' | 'children' | 'meta'>\n    : never;\n\n  type RouteModule = Record<string, { default: Route }>;\n}\n\ndeclare namespace AuthRouteUtils {\n  type RouteKeySplitMark = '_';\n\n  type RoutePathSplitMark = '/';\n\n  type BlankString = '';\n\n  /** key转换成path */\n  type KeyToPath<K extends string> = K extends `${infer _Left}${RouteKeySplitMark}${RouteKeySplitMark}${infer _Right}`\n    ? never\n    : K extends `${infer Left}${RouteKeySplitMark}${infer Right}`\n      ? Left extends BlankString\n        ? never\n        : Right extends BlankString\n          ? never\n          : KeyToPath<`${Left}${RoutePathSplitMark}${Right}`>\n      : `${RoutePathSplitMark}${K}`;\n\n  /** 根据路由key获取路由路径 */\n  type GetRoutePath<K extends AuthRoute.AllRouteKey = AuthRoute.AllRouteKey> = K extends AuthRoute.AllRouteKey\n    ? K extends AuthRoute.RootRouteKey\n      ? AuthRoute.RootRoutePath\n      : K extends AuthRoute.NotFoundRouteKey\n        ? AuthRoute.NotFoundRoutePath\n        : KeyToPath<K>\n    : never;\n\n  /** 获取一级路由(有子路由的一级路由和没有子路由的路由) */\n  type GetFirstDegreeRouteKey<K extends AuthRoute.RouteKey = AuthRoute.RouteKey> =\n    K extends `${infer _Left}${RouteKeySplitMark}${infer _Right}` ? never : K;\n\n  /** 获取有子路由的一级路由 */\n  type GetFirstDegreeRouteKeyWithChildren<K extends AuthRoute.RouteKey = AuthRoute.RouteKey> =\n    K extends `${infer Left}${RouteKeySplitMark}${infer _Right}` ? Left : never;\n\n  /** 单级路由的key (单级路由需要添加一个父级路由用于应用布局组件) */\n  type SingleRouteKey = Exclude<GetFirstDegreeRouteKey,\n    GetFirstDegreeRouteKeyWithChildren | AuthRoute.RootRouteKey | AuthRoute.NotFoundRouteKey>;\n\n  /** 单独路由父级路由key */\n  type SingleRouteParentKey = `${SingleRouteKey}-parent`;\n\n  /** 单独路由父级路由path */\n  type SingleRouteParentPath = KeyToPath<SingleRouteParentKey>;\n\n  /** 获取路由动态路径 */\n  type GetDynamicPath<P extends AuthRoute.RoutePath> =\n    | `${P}/:${string}`\n    | `${P}/:${string}(${string})`\n    | `${P}/:${string}(${string})?`;\n}\n"
  },
  {
    "path": "src/typings/router.d.ts",
    "content": "import 'vue-router';\n\ndeclare module 'vue-router' {\n  interface RouteMeta extends AuthRoute.RouteMeta<AuthRoute.RoutePath> {}\n}\n"
  },
  {
    "path": "src/typings/storage.d.ts",
    "content": "declare namespace StorageInterface {\n  /** localStorage的存储数据的类型 */\n  interface Session {\n    demoKey: string;\n  }\n\n  /** localStorage的存储数据的类型 */\n  interface Local {\n    token: string;\n    refreshToken: string;\n    userInfo: Auth.UserInfo;\n    themeSettings: ThemeConfig.Config;\n  }\n}\n"
  },
  {
    "path": "src/typings/system.d.ts",
    "content": "/** 枚举的key类型 */\ndeclare namespace EnumType {\n  /** 布局组件名称 */\n  type LayoutComponentName = keyof typeof import('@/enum').EnumLayoutComponentName;\n\n  /** 布局模式 */\n  type ThemeLayoutMode = keyof typeof import('@/enum').EnumThemeLayoutMode;\n\n  /** 多页签风格 */\n  type ThemeTabMode = keyof typeof import('@/enum').EnumThemeTabMode;\n\n  /** 水平模式的菜单位置 */\n  type ThemeHorizontalMenuPosition = keyof typeof import('@/enum').EnumThemeHorizontalMenuPosition;\n\n  /** 过渡动画 */\n  type ThemeAnimateMode = keyof typeof import('@/enum').EnumThemeAnimateMode;\n\n  /** 登录模块 */\n  type LoginModuleKey = keyof typeof import('@/enum').EnumLoginModule;\n}\n\n/** 请求的相关类型 */\ndeclare namespace Service {\n  /**\n   * 请求的错误类型：\n   * - axios: axios错误：网络错误, 请求超时, 默认的兜底错误\n   * - http: 请求成功，响应的http状态码非200的错误\n   * - backend: 请求成功，响应的http状态码为200，由后端定义的业务错误\n   */\n  type RequestErrorType = 'axios' | 'http' | 'backend';\n\n  /** 请求错误 */\n  interface RequestError {\n    /** 请求服务的错误类型 */\n    type: RequestErrorType;\n    /** 错误码 */\n    code: string | number;\n    /** 错误信息 */\n    msg: string;\n  }\n\n  /** 后端接口返回的数据结构配置 */\n  interface BackendResultConfig {\n    /** 表示后端请求状态码的属性字段 */\n    codeKey: string;\n    /** 表示后端请求数据的属性字段 */\n    dataKey: string;\n    /** 表示后端消息的属性字段 */\n    msgKey: string;\n    /** 后端业务上定义的成功请求的状态 */\n    successCode: number | string;\n  }\n\n  /** 自定义的请求成功结果 */\n  interface SuccessResult<T = any> {\n    /** 请求错误 */\n    error: null;\n    /** 请求数据 */\n    data: T;\n  }\n\n  /** 自定义的请求失败结果 */\n  interface FailedResult {\n    /** 请求错误 */\n    error: RequestError;\n    /** 请求数据 */\n    data: null;\n  }\n\n  /** 自定义的请求结果 */\n  type RequestResult<T = any> = SuccessResult<T> | FailedResult;\n\n  /** 多个请求数据结果 */\n  type MultiRequestResult<T extends any[]> = T extends [infer First, ...infer Rest]\n    ? [First] extends [any]\n      ? Rest extends any[]\n        ? [Service.RequestResult<First>, ...MultiRequestResult<Rest>]\n        : [Service.RequestResult<First>]\n      : Rest extends any[]\n        ? MultiRequestResult<Rest>\n        : []\n    : [];\n\n  /** 请求结果的适配器函数 */\n  type ServiceAdapter<T = any, A extends any[] = any> = (...args: A) => T;\n\n  /** mock示例接口类型：后端接口返回的数据的类型 */\n  interface MockServiceResult<T = any> {\n    /** 状态码 */\n    code: string | number;\n    /** 接口数据 */\n    data: T;\n    /** 接口消息 */\n    message: string;\n  }\n\n  /** mock的响应option */\n  interface MockOption {\n    url: Record<string, any>;\n    body: Record<string, any>;\n    query: Record<string, any>;\n    headers: Record<string, any>;\n  }\n}\n\n/** 主题相关类型 */\ndeclare namespace Theme {\n\n  /** 布局样式 */\n  interface Layout {\n    /** 最小宽度 */\n    minWidth: number;\n    /** 布局模式 */\n    mode: EnumType.ThemeLayoutMode;\n    /** 布局模式列表 */\n    modeList: LayoutModeList[];\n  }\n\n  interface LayoutModeList {\n    value: EnumType.ThemeLayoutMode;\n    label: import('@/enum').EnumThemeLayoutMode;\n  }\n\n  /** 其他主题颜色 */\n  interface OtherColor {\n    /** 信息 */\n    info: string;\n    /** 成功 */\n    success: string;\n    /** 警告 */\n    warning: string;\n    /** 错误 */\n    error: string;\n  }\n\n  /** 头部样式 */\n  interface Header {\n    /** 头部反转色 */\n    inverted: boolean;\n    /** 头部高度 */\n    height: number;\n    /** 面包屑样式 */\n    crumb: Crumb;\n  }\n\n  /** 面包屑样式 */\n  interface Crumb {\n    /** 面包屑可见 */\n    visible: boolean;\n    /** 显示图标 */\n    showIcon: boolean;\n  }\n\n  /** 标多页签样式 */\n  export interface Tab {\n    /** 多页签可见 */\n    visible: boolean;\n    /** 多页签高度 */\n    height: number;\n    /** 多页签风格 */\n    mode: EnumType.ThemeTabMode;\n    /** 多页签风格列表 */\n    modeList: ThemeTabModeList[];\n    /** 开启多页签缓存 */\n    isCache: boolean;\n  }\n\n  /** 多页签风格列表 */\n  interface ThemeTabModeList {\n    value: EnumType.ThemeTabMode;\n    label: import('@/enum').EnumThemeTabMode;\n  }\n\n  /** 侧边栏样式 */\n  interface Sider {\n    /** 侧边栏反转色 */\n    inverted: boolean;\n    /** 侧边栏宽度 */\n    width: number;\n    /** 侧边栏折叠时的宽度 */\n    collapsedWidth: number;\n    /** vertical-mix模式下侧边栏宽度 */\n    mixWidth: number;\n    /** vertical-mix模式下侧边栏折叠时的宽度 */\n    mixCollapsedWidth: number;\n    /** vertical-mix模式下侧边栏的子菜单的宽度 */\n    mixChildMenuWidth: number;\n  }\n\n  /** 菜单样式 */\n  interface Menu {\n    /** 水平模式的菜单的位置 */\n    horizontalPosition: EnumType.ThemeHorizontalMenuPosition;\n    /** 水平模式的菜单的位置列表 */\n    horizontalPositionList: HorizontalMenuPositionList[];\n  }\n\n  /** 水平模式的菜单的位置列表 */\n  interface HorizontalMenuPositionList {\n    value: EnumType.ThemeHorizontalMenuPosition;\n    label: import('@/enum').EnumThemeHorizontalMenuPosition;\n  }\n\n  /** 底部样式 */\n  interface Footer {\n    /** 是否固定底部 */\n    fixed: boolean;\n    /** 底部高度 */\n    height: number;\n    /* 底部是否可见 */\n    visible: boolean;\n  }\n\n  /** 页面样式 */\n  interface Page {\n    /** 页面是否开启动画 */\n    animate: boolean;\n    /** 动画类型 */\n    animateMode: EnumType.ThemeAnimateMode;\n    /** 动画类型列表 */\n    animateModeList: AnimateModeList[];\n  }\n\n  /** 动画类型列表 */\n  interface AnimateModeList {\n    value: EnumType.ThemeAnimateMode;\n    label: import('@/enum').EnumThemeAnimateMode;\n  }\n}\n\ndeclare namespace App {\n  /** 全局头部属性 */\n  interface GlobalHeaderProps {\n    /** 显示logo */\n    showLogo: boolean;\n    /** 显示头部菜单 */\n    showHeaderMenu: boolean;\n    /** 显示菜单折叠按钮 */\n    showMenuCollapse: boolean;\n  }\n\n  type GlobalMenuOption = {\n    key: string;\n    label: string;\n    routeName: string;\n    routePath: string;\n    icon?: string;\n    children?: GlobalMenuOption[];\n  };\n\n  /** 面包屑 */\n  type GlobalBreadcrumb = BreadcrumbItem\n\n  /** 多页签Tab的路由 */\n  interface GlobalTabRoute\n    extends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'fullPath' | 'meta'> {\n    /** 滚动的位置 */\n    scrollPosition: {\n      left: number;\n      top: number;\n    };\n  }\n}\n\ndeclare namespace I18nType {\n  interface Schema {\n    system: {\n      title: string;\n    };\n    routes: {\n      dashboard: {\n        dashboard: string;\n        analysis: string;\n        workbench: string;\n      };\n      about: {\n        about: string;\n      };\n    };\n  }\n}\n"
  },
  {
    "path": "src/typings/utils.d.ts",
    "content": "declare namespace TypeUtil {\n  type Noop = (...args: any) => any;\n\n  type UnionInclude<T, K extends keyof T> = K extends keyof T ? true : false;\n\n  type GetFunArgs<F extends Noop> = F extends (...args: infer P) => any ? P : never;\n\n  type Writable<T> = { [K in keyof T]: T[K] };\n\n  type FirstOfArray<T extends any[]> = T extends [infer First, ...infer _Rest] ? First : never;\n\n  type LastOfArray<T extends any[]> = T extends [...infer _Rest, infer Last] ? Last : never;\n\n  // union to tuple\n  type Union2IntersectionFn<T> = (T extends unknown ? (k: () => T) => void : never) extends (k: infer R) => void\n    ? R\n    : never;\n  type GetUnionLast<U> = Union2IntersectionFn<U> extends () => infer I ? I : never;\n\n  type UnionToTuple<T, R extends any[] = []> = [T] extends [never]\n    ? R\n    : UnionToTuple<Exclude<T, GetUnionLast<T>>, [GetUnionLast<T>, ...R]>;\n}\n"
  },
  {
    "path": "src/typings/vuetify.d.ts",
    "content": "// export vuetify types.ts for ts\n\ntype BreadcrumbItem = import('vuetify/components').VBreadcrumbsItem['$props']\ntype DataTableHeader = import('vuetify/labs/VDataTable').VDataTable['headers']\ntype Snackbar = import('vuetify/components').VSnackbar['$props']\ntype Dialog = import('vuetify/components').VDialog['$props']\ntype VCardTitle = import('vuetify/components').VCardTitle\ntype VTextField = import('vuetify/components').VTextField\ntype VSelect = import('vuetify/components').VSelect\ntype VRadio= import('vuetify/components').VRadio\ntype VCheckbox= import('vuetify/components').VCheckbox\ntype VBtn = import('vuetify/components').VBtn\ntype VCol= import('vuetify/components').VCol\n\n"
  },
  {
    "path": "src/utils/common/index.ts",
    "content": "export * from './typeof';\nexport * from './pattern';\n"
  },
  {
    "path": "src/utils/common/pattern.ts",
    "content": "export function exeStrategyActions(actions: Common.StrategyAction[]) {\n  actions.some(item => {\n    const [flag, action] = item;\n    if (flag) {\n      action();\n    }\n    return flag;\n  });\n}\n"
  },
  {
    "path": "src/utils/common/typeof.ts",
    "content": "import { EnumDataType } from '@/enum';\n\nexport function isNumber<T extends number>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.number;\n}\n\nexport function isString<T extends string>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.string;\n}\n\nexport function isBoolean<T extends boolean>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.boolean;\n}\n\nexport function isNull<T extends null>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.null;\n}\n\nexport function isUndefined<T extends undefined>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.undefined;\n}\n\nexport function isObject<T extends Record<string, any>>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.object;\n}\n\nexport function isArray<T extends any[]>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.array;\n}\n\nexport function isFunction<T extends (...args: any[]) => any | void | never>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.function;\n}\n\nexport function isDate<T extends Date>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.date;\n}\n\nexport function isRegExp<T extends RegExp>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.regexp;\n}\n\nexport function isPromise<T extends Promise<any>>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.promise;\n}\n\nexport function isSet<T extends Set<any>>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.set;\n}\n\nexport function isMap<T extends Map<any, any>>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.map;\n}\n\nexport function isFile<T extends File>(data: T | unknown): data is T {\n  return Object.prototype.toString.call(data) === EnumDataType.file;\n}\n"
  },
  {
    "path": "src/utils/crypto/index.ts",
    "content": "import CryptoJS from 'crypto-js';\n\nconst CryptoSecret = '__CryptoJS_Secret__';\n\n/**\n * 加密数据\n * @param data - 数据\n */\nexport function encrypto(data: any) {\n  const newData = JSON.stringify(data);\n  return CryptoJS.AES.encrypt(newData, CryptoSecret).toString();\n}\n\n/**\n * 解密数据\n * @param cipherText - 密文\n */\nexport function decrypto(cipherText: string) {\n  const bytes = CryptoJS.AES.decrypt(cipherText, CryptoSecret);\n  const originalText = bytes.toString(CryptoJS.enc.Utf8);\n  if (originalText) {\n    return JSON.parse(originalText);\n  }\n  return null;\n}\n"
  },
  {
    "path": "src/utils/flow/EventEmitter.ts",
    "content": "import {getRawType, notNull} from './tools'\n\nconst isArray = (obj: any) => getRawType(obj) === 'array'\nconst isNullOrUndefined = (obj: any) => !notNull(obj)\n\nclass EventEmitter {\n  static _events: { [key: string]: Function[] } = {}\n\n  constructor() {\n  }\n\n  static _addListener(type: string, fn: any, context?: any, once?: any) {\n    if (typeof fn !== 'function') {\n      throw new TypeError('fn must be a function')\n    }\n\n    fn.context = context\n    fn.once = !!once\n\n    const event = EventEmitter._events[type]\n    // only one, let `this._events[type]` to be a function\n    if (isNullOrUndefined(event)) {\n      EventEmitter._events[type] = fn\n    } else if (typeof event === 'function') {\n      // already has one function, `this._events[type]` must be a function before\n      EventEmitter._events[type] = [event, fn]\n    } else if (isArray(event)) {\n      // already has more than one function, just push\n      EventEmitter._events[type].push(fn)\n    }\n\n    return EventEmitter\n  }\n\n  static addListener(type: string, fn: any, context?: any) {\n    return EventEmitter._addListener(type, fn, context)\n  }\n\n  static on(type: string, fn: any, context?: any) {\n    return EventEmitter.addListener(type, fn, context)\n  }\n\n  static once(type: string, fn: any, context?: any) {\n    return EventEmitter._addListener(type, fn, context, true)\n  }\n\n  static emit(type: any, ...rest: any[]) {\n    if (isNullOrUndefined(type)) {\n      throw new Error('emit must receive at lease one argument')\n    }\n\n    const event: any = EventEmitter._events[type]\n\n    if (isNullOrUndefined(event)) return false\n\n    if (typeof event === 'function') {\n      event.call(event.context || null, ...rest)\n      if (event.once) {\n        EventEmitter.removeListener(type, event)\n      }\n    } else if (isArray(event)) {\n      event.map((e: any) => {\n        e.call(e.context || null, ...rest)\n        if (e.once) {\n          EventEmitter.removeListener(type, e)\n        }\n      })\n    }\n\n    return true\n  }\n\n  static removeListener(type: any, fn: any) {\n    if (isNullOrUndefined(EventEmitter._events)) return EventEmitter\n\n    // if type is undefined or null, nothing to do, just return this\n    if (isNullOrUndefined(type)) return EventEmitter\n\n    if (typeof fn !== 'function') {\n      throw new Error('fn must be a function')\n    }\n\n    const events = EventEmitter._events[type]\n\n    if (typeof events === 'function') {\n      events === fn && delete EventEmitter._events[type]\n    } else {\n      const findIndex = events.findIndex((e) => e === fn)\n\n      if (findIndex === -1) return EventEmitter\n\n      // match the first one, shift faster than splice\n      if (findIndex === 0) {\n        events.shift()\n      } else {\n        events.splice(findIndex, 1)\n      }\n\n      // just left one listener, change Array to Function\n      if (events.length === 1) {\n        // @ts-ignore\n        EventEmitter._events[type] = events[0]\n      }\n    }\n\n    return EventEmitter\n  }\n\n  static removeAllListeners(type: any) {\n    if (isNullOrUndefined(EventEmitter._events)) return EventEmitter\n\n    // if not provide type, remove all\n    if (isNullOrUndefined(type)) EventEmitter._events = Object.create(null)\n\n    const events = EventEmitter._events[type]\n    if (!isNullOrUndefined(events)) {\n      // check if `type` is the last one\n      if (Object.keys(EventEmitter._events).length === 1) {\n        EventEmitter._events = Object.create(null)\n      } else {\n        delete EventEmitter._events[type]\n      }\n    }\n\n    return EventEmitter\n  }\n\n  static listeners(type: any) {\n    if (isNullOrUndefined(EventEmitter._events)) return []\n\n    const events = EventEmitter._events[type]\n    // use `map` because we need to return a new array\n    return isNullOrUndefined(events)\n      ? []\n      : typeof events === 'function'\n        ? [events]\n        : events.map((o) => o)\n  }\n\n  static listenerCount(type: any) {\n    if (isNullOrUndefined(EventEmitter._events)) return 0\n\n    const events = EventEmitter._events[type]\n\n    return isNullOrUndefined(events) ? 0 : typeof events === 'function' ? 1 : events.length\n  }\n\n  static eventNames() {\n    if (isNullOrUndefined(EventEmitter._events)) return []\n\n    return Object.keys(EventEmitter._events)\n  }\n}\n\nexport default EventEmitter\n"
  },
  {
    "path": "src/utils/flow/tools.ts",
    "content": "/* 空函数 */\nexport function noop() {\n}\n\n/**\n * 校验非空\n * @param {*} val\n * @return boolean\n */\nexport function notEmpty(val: any) {\n  if (!notNull(val)) {\n    return false\n  }\n  if (getRawType(val) === 'array') {\n    return val.length\n  }\n  if (getRawType(val) === 'object') {\n    return Reflect.ownKeys(val).length\n  }\n  return true\n}\n\nexport function notNull(val: any) {\n  return val !== undefined && val !== null\n}\n\n/**\n * 返回数据原始类型\n * @param value\n * @return { 'string' | 'array' | 'boolean' | 'number' | 'object' | 'function' } type\n */\nexport function getRawType(value: any) {\n  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()\n}\n"
  },
  {
    "path": "src/utils/index.ts",
    "content": "export * from './service';\nexport * from './storage';\nexport * from './router';\nexport * from './common';\nexport * from './vue';\n"
  },
  {
    "path": "src/utils/router/auth.ts",
    "content": "export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {\n  return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);\n}\n\nfunction filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {\n  const filterRoute = { ...route };\n  const hasPermission =\n    !route.meta.permissions || permission === 'admin' || route.meta.permissions.includes(permission);\n\n  if (filterRoute.children) {\n    const filterChildren = filterRoute.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);\n    Object.assign(filterRoute, { children: filterChildren });\n  }\n  return hasPermission ? [filterRoute] : [];\n}\n"
  },
  {
    "path": "src/utils/router/breadcrumb.ts",
    "content": "/**\n * 获取面包屑数据\n * @param activeKey - 当前页面路由的key\n * @param menus - 菜单数据\n * @param rootPath - 根路由路径\n */\nexport function getBreadcrumbByRouteKey(activeKey: string, menus: App.GlobalMenuOption[], rootPath: string) {\n  const breadcrumbMenu = getBreadcrumbMenu((menu) => activeKey.includes(menu.routeName), menus);\n  const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));\n  return breadcrumb;\n}\n\nexport function getBreadcrumbsByPredicate(predicate: (menu: App.GlobalMenuOption) => boolean, menus: App.GlobalMenuOption[], rootPath: string) {\n  const breadcrumbMenu = getBreadcrumbMenu(predicate, menus);\n  const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));\n  return breadcrumb;\n}\n\n/**\n * 根据菜单数据获取面包屑格式的菜单\n * @param predicate\n * @param menus - 菜单数据\n */\nfunction getBreadcrumbMenu(predicate: (menu: App.GlobalMenuOption) => boolean, menus: App.GlobalMenuOption[]) {\n  const breadcrumbMenu: App.GlobalMenuOption[] = [];\n  menus.some(menu => {\n    const flag = predicate(menu);\n    if (flag) {\n      breadcrumbMenu.push(...getBreadcrumbMenuItem(predicate, menu));\n    }\n    return flag;\n  });\n  return breadcrumbMenu;\n}\n\n/**\n * 根据单个菜单数据获取面包屑格式的菜单\n * @param predicate\n * @param menu - 单个菜单数据\n */\nfunction getBreadcrumbMenuItem(predicate: (menu: App.GlobalMenuOption) => boolean, menu: App.GlobalMenuOption) {\n  const breadcrumbMenu: App.GlobalMenuOption[] = [];\n  if (predicate(menu)) {\n    breadcrumbMenu.push(menu);\n  }\n  if (predicate(menu) && menu.children && menu.children.length) {\n    breadcrumbMenu.push(\n      ...menu.children.map(item => getBreadcrumbMenuItem(predicate, item as App.GlobalMenuOption)).flat(1)\n    );\n  }\n\n  return breadcrumbMenu;\n}\n\n/**\n * 将面包屑格式的菜单数据转换成面包屑数据\n * @param menu - 单个菜单数据\n * @param rootPath - 根路由路径\n */\nfunction transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPath: string) {\n  const breadcrumb: App.GlobalBreadcrumb = {\n    key: menu.routeName,\n    title: menu.label,\n    to: menu.routePath,\n    disabled: menu.routePath === rootPath,\n  };\n  return breadcrumb;\n}\n"
  },
  {
    "path": "src/utils/router/cache.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router';\n\n/**\n * 获取缓存的路由对应组件的名称\n * @param routes - 转换后的vue路由\n */\nexport function getCacheRoutes(routes: RouteRecordRaw[]) {\n  const cacheNames: string[] = [];\n  routes.forEach(route => {\n    // 只需要获取二级路由的缓存的组件名\n    if (hasChildren(route)) {\n      (route.children as RouteRecordRaw[]).forEach(item => {\n        if (isKeepAlive(item)) {\n          cacheNames.push(item.name as string);\n        }\n      });\n    }\n  });\n  return cacheNames;\n}\n\n/**\n * 路由是否缓存\n * @param route\n */\nfunction isKeepAlive(route: RouteRecordRaw) {\n  return Boolean(route?.meta?.keepAlive);\n}\n/**\n * 是否有二级路由\n * @param route\n */\nfunction hasChildren(route: RouteRecordRaw) {\n  return Boolean(route.children && route.children.length);\n}\n"
  },
  {
    "path": "src/utils/router/component.ts",
    "content": "import type {RouteComponent} from 'vue-router';\nimport {views} from '@/views';\nimport {isFunction} from '@/utils';\nimport {BasicLayout, BlankLayout, AuthLayout, ErrorLayout, TodoLayout, ChatLayout} from '@/layouts';\n\ntype Lazy<T> = () => Promise<T>;\n\ninterface ModuleComponent {\n  default: RouteComponent;\n}\n\ntype LayoutComponent = Record<EnumType.LayoutComponentName, Lazy<ModuleComponent>>;\n\n/**\n * 获取布局的vue文件(懒加载的方式)\n * @param layoutType - 布局类型\n */\nexport function getLayoutComponent(layoutType: EnumType.LayoutComponentName) {\n  const layoutComponent: LayoutComponent = {\n    basic: BasicLayout,\n    blank: BlankLayout,\n    auth: AuthLayout,\n    error: ErrorLayout,\n    todo: TodoLayout,\n    chat: ChatLayout,\n  };\n  return layoutComponent[layoutType];\n}\n\n/**\n * 获取页面导入的vue文件\n * @param routeKey - 路由key\n */\nexport function getViewComponent(routeKey: AuthRoute.LastDegreeRouteKey) {\n  if (!views[routeKey]) {\n    throw new Error(`route “${routeKey}” miss corresponding component file`);\n  }\n  return setViewComponentName(views[routeKey], routeKey);\n}\n\nfunction setViewComponentName(component: RouteComponent | Lazy<ModuleComponent>, name: string) {\n  if (isAsyncComponent(component)) {\n    return async () => {\n      const result = await component();\n      Object.assign(result.default, {name});\n      return result;\n    };\n  }\n\n  Object.assign(component, {name});\n\n  return component;\n}\n\nfunction isAsyncComponent(component: RouteComponent | Lazy<ModuleComponent>): component is Lazy<ModuleComponent> {\n  return isFunction(component);\n}\n"
  },
  {
    "path": "src/utils/router/helpers.ts",
    "content": "/**\n * 获取所有固定路由的名称集合\n * @param routes - 固定路由\n */\nexport function getConstantRouteNames(routes: AuthRoute.Route[]) {\n  return routes.map(route => getConstantRouteName(route)).flat(1);\n}\n\n/**\n * 获取所有固定路由的名称集合\n * @param route - 固定路由\n */\nfunction getConstantRouteName(route: AuthRoute.Route) {\n  const names = [route.name];\n  if (route.children?.length) {\n    names.push(...route.children!.map(item => getConstantRouteName(item)).flat(1));\n  }\n  return names;\n}\n"
  },
  {
    "path": "src/utils/router/index.ts",
    "content": "export * from './transform';\nexport * from './breadcrumb';\nexport * from './component';\nexport * from './regexp';\nexport * from './cache';\nexport * from './helpers';\nexport * from './auth';\nexport * from './module';\nexport * from './menu';\n"
  },
  {
    "path": "src/utils/router/menu.ts",
    "content": "/**\n * 将权限路由转换成菜单\n * @param routes - 路由\n */\nexport function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalMenuOption[] {\n  const globalMenu: App.GlobalMenuOption[] = [];\n  routes.forEach(route => {\n    const {name, path, meta} = route;\n    const routePath = route.meta.dynamicPath || path\n    const routeName = name as string;\n    let menuChildren: App.GlobalMenuOption[] | undefined;\n    if (route.children) {\n      menuChildren = transformAuthRouteToMenu(route.children);\n    }\n    const menuItem: App.GlobalMenuOption = addPartialProps({\n      menu: {\n        key: routeName,\n        label: meta.title,\n        routeName,\n        routePath: routePath\n      },\n      icon: meta.icon,\n      children: menuChildren\n    });\n\n    if (!hideInMenu(route)) {\n      globalMenu.push(menuItem);\n    }\n  });\n\n\n  return globalMenu;\n}\n\n/**\n * 获取当前路由所在菜单数据的paths\n * @param activeKey - 当前路由的key\n * @param menus - 菜单数据\n */\nexport function getActiveKeyPathsOfMenus(activeKey: string, menus: App.GlobalMenuOption[]) {\n  const keys = menus.map(menu => getActiveKeyPathsOfMenu(activeKey, menu)).flat(1);\n  return keys;\n}\n\nfunction getActiveKeyPathsOfMenu(activeKey: string, menu: App.GlobalMenuOption) {\n  const keys: string[] = [];\n  if (activeKey.startsWith(menu.routeName)) {\n    keys.push(menu.routeName);\n  }\n  if (menu.children) {\n    keys.push(...menu.children.map(item => getActiveKeyPathsOfMenu(activeKey, item as App.GlobalMenuOption)).flat(1));\n  }\n  return keys;\n}\n\n/** 路由不转换菜单 */\nfunction hideInMenu(route: AuthRoute.Route) {\n  return Boolean(route.meta.hide);\n}\n\n/** 给菜单添加可选属性 */\nfunction addPartialProps(config: {\n  menu: App.GlobalMenuOption;\n  icon?: string;\n  children?: App.GlobalMenuOption[];\n}) {\n\n  const item = {...config.menu};\n\n  const {icon, children} = config;\n\n  if (icon) {\n    Object.assign(item, {icon});\n  }\n\n  if (children) {\n    Object.assign(item, {children});\n  }\n  return item;\n}\n"
  },
  {
    "path": "src/utils/router/module.ts",
    "content": "/**\n * 权限路由排序\n * @param routes - 权限路由\n */\nexport function sortRoutes(routes: AuthRoute.Route[]) {\n  return routes.sort((next, pre) => {\n    return Number(next.meta?.order) - Number(pre.meta?.order)\n  }).map(i => {\n    if (i.children)\n      sortRoutes(i.children)\n    return i\n  })\n}\n\n/**\n * 处理全部导入的路由模块\n * @param modules - 路由模块\n */\nexport function handleModuleRoutes(modules: AuthRoute.RouteModule) {\n  const routes: AuthRoute.Route[] = [];\n\n  Object.keys(modules).forEach(key => {\n    const item = modules[key].default;\n    if (item) {\n      routes.push(item);\n    } else {\n      window.console.error(`路由模块解析出错: key = ${key}`);\n    }\n  });\n\n  return sortRoutes(routes);\n}\n"
  },
  {
    "path": "src/utils/router/regexp.ts",
    "content": "/** 获取登录页面模块的动态路由的正则 */\nexport function getLoginModuleRegExp() {\n  const modules: EnumType.LoginModuleKey[] = ['forgot', 'verify-email', 'sign-up', 'reset', 'sign-in'];\n  return modules.join('|');\n}\n"
  },
  {
    "path": "src/utils/router/transform.ts",
    "content": "import type {RouteRecordRaw} from 'vue-router';\nimport {getLayoutComponent, getViewComponent} from '@/utils';\nimport i18n from \"@/plugins/vue-i18n\";\n\nexport function transformAuthRouteToVueRoutes(routes: AuthRoute.Route[]) {\n  return routes.map(route => transformAuthRouteToVueRoute(route)).flat(1);\n}\n\ntype ComponentAction = Record<AuthRoute.RouteComponentType, () => void>;\n\nexport function transformAuthRouteToVueRoute(item: AuthRoute.Route) {\n  const resultRoute: RouteRecordRaw[] = [];\n\n  const itemRoute = {...item} as RouteRecordRaw;\n\n  if (hasDynamicPath(item)) {\n    Object.assign(itemRoute, {path: item.meta.dynamicPath});\n  }\n\n  if (hasHref(item)) {\n    Object.assign(itemRoute, {component: getViewComponent('404')});\n  }\n\n  if (hasComponent(item)) {\n    const action: ComponentAction = {\n      basic() {\n        itemRoute.component = getLayoutComponent('basic');\n      },\n      blank() {\n        itemRoute.component = getLayoutComponent('blank');\n      },\n      auth() {\n        itemRoute.component = getLayoutComponent('auth');\n      },\n      error() {\n        itemRoute.component = getLayoutComponent('error');\n      },\n      todo() {\n        itemRoute.component = getLayoutComponent('todo');\n      },\n      chat() {\n        itemRoute.component = getLayoutComponent('chat');\n      },\n      self() {\n        itemRoute.component = getViewComponent(item.name as AuthRoute.LastDegreeRouteKey);\n      }\n    };\n    try {\n      if (item.component) {\n        action[item.component]();\n      } else {\n        window.console.error('router resolve failed: ', item);\n      }\n    } catch {\n      window.console.error('router resolve failed: ', item);\n    }\n  }\n\n  if (isSingleRoute(item)) {\n    if (hasChildren(item)) {\n      window.console.error('singleRoute should not has child: ', item);\n    }\n\n    if (item.name === 'not-found') {\n      itemRoute.children = [\n        {\n          path: '',\n          name: item.name,\n          component: getViewComponent('not-found')\n        }\n      ];\n    } else {\n\n      //prepare parent layout\n      const parentPath = `${itemRoute.path}-parent` as AuthRouteUtils.SingleRouteKey;\n\n      let layout\n      switch (item.meta.singleLayout) {\n        case 'blank':\n          layout = getLayoutComponent('blank')\n          break\n        case 'auth':\n          layout = getLayoutComponent('auth')\n          break\n        case 'error':\n          layout = getLayoutComponent('error')\n          break\n        case 'basic':\n          layout = getLayoutComponent('basic')\n          break\n        case 'todo':\n          layout = getLayoutComponent('todo')\n          break\n        case 'chat':\n          layout = getLayoutComponent('chat')\n          break\n        default:\n          layout = getLayoutComponent('blank')\n      }\n      const parentRoute: RouteRecordRaw = {\n        path: parentPath,\n        component: layout,\n        redirect: item.path,\n        children: [itemRoute]\n      };\n\n      return [parentRoute];\n    }\n  }\n\n  if (hasChildren(item)) {\n    const children = (item.children as AuthRoute.Route[]).map(child => transformAuthRouteToVueRoute(child)).flat();\n\n    const redirectPath = (children.find(v => !v.meta?.multi)?.path || '/') as AuthRoute.RoutePath;\n\n    if (redirectPath === '/') {\n      window.console.error('could not found effective child in multiple router ', item);\n    }\n\n    itemRoute.children = children;\n    itemRoute.redirect = redirectPath;\n  }\n\n  resultRoute.push(itemRoute);\n\n  return resultRoute;\n}\n\nexport function transformAuthRouteToSearchMenus(routes: AuthRoute.Route[], treeMap: AuthRoute.Route[] = [], parentTitle: string = '') {\n  if (routes && routes.length === 0) return [];\n  return routes.reduce((acc, cur) => {\n    const parent = parentTitle.length > 0 ? parentTitle + \"/\" : parentTitle\n    const title = parent + i18n.global.t(cur.meta.title)\n    if (!cur.meta?.hide && (!cur.children || cur.children?.length == 0)) {\n      acc.push({\n        ...cur,\n        meta: {\n          ...cur,\n          title,\n        }\n      });\n    }\n    if (cur.children && cur.children.length > 0) {\n      transformAuthRouteToSearchMenus(cur.children, treeMap, title);\n    }\n    return acc;\n  }, treeMap);\n}\n\nexport function transformRouteNameToRoutePath(name: Exclude<AuthRoute.AllRouteKey, 'not-found'>): AuthRoute.RoutePath {\n  const rootPath: AuthRoute.RoutePath = '/';\n  if (name === 'root') return rootPath;\n\n  const splitMark = '_';\n  const pathSplitMark = '/';\n  const path = name.split(splitMark).join(pathSplitMark);\n\n  return (pathSplitMark + path) as AuthRoute.RoutePath;\n}\n\nexport function transformRoutePathToRouteName<K extends AuthRoute.RoutePath>(path: K) {\n  if (path === '/') return 'root';\n\n  const pathSplitMark = '/';\n  const routeSplitMark = '_';\n\n  const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.AllRouteKey;\n\n  return name;\n}\n\nfunction hasHref(item: AuthRoute.Route) {\n  return Boolean(item.meta.href);\n}\n\nfunction hasDynamicPath(item: AuthRoute.Route) {\n  return Boolean(item.meta.dynamicPath);\n}\n\nfunction hasComponent(item: AuthRoute.Route) {\n  return Boolean(item.component);\n}\n\nfunction hasChildren(item: AuthRoute.Route) {\n  return Boolean(item.children && item.children.length);\n}\n\nfunction isSingleRoute(item: AuthRoute.Route) {\n  return Boolean(item.meta.singleLayout);\n}\n"
  },
  {
    "path": "src/utils/service/error.ts",
    "content": "import type { AxiosError, AxiosResponse } from 'axios';\nimport {\n  DEFAULT_REQUEST_ERROR_CODE,\n  DEFAULT_REQUEST_ERROR_MSG,\n  ERROR_STATUS,\n  NETWORK_ERROR_CODE,\n  NETWORK_ERROR_MSG,\n  REQUEST_TIMEOUT_CODE,\n  REQUEST_TIMEOUT_MSG\n} from '@/configs/service';\nimport { exeStrategyActions } from '../common';\nimport { showErrorMsg } from './msg';\n\ntype ErrorStatus = keyof typeof ERROR_STATUS;\n\n/**\n * 处理axios请求失败的错误\n * @param axiosError - 错误\n */\nexport function handleAxiosError(axiosError: AxiosError) {\n  const error: Service.RequestError = {\n    type: 'axios',\n    code: DEFAULT_REQUEST_ERROR_CODE,\n    msg: DEFAULT_REQUEST_ERROR_MSG\n  };\n\n  const actions: Common.StrategyAction[] = [\n    [\n      // 网路错误\n      !window.navigator.onLine || axiosError.message === 'Network Error',\n      () => {\n        Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });\n      }\n    ],\n    [\n      // 超时错误\n      axiosError.code === REQUEST_TIMEOUT_CODE && axiosError.message.includes('timeout'),\n      () => {\n        Object.assign(error, { code: REQUEST_TIMEOUT_CODE, msg: REQUEST_TIMEOUT_MSG });\n      }\n    ],\n    [\n      // 请求不成功的错误\n      Boolean(axiosError.response),\n      () => {\n        const errorCode: ErrorStatus = (axiosError.response?.status as ErrorStatus) || 'DEFAULT';\n        const msg = ERROR_STATUS[errorCode];\n        Object.assign(error, { code: errorCode, msg });\n      }\n    ]\n  ];\n\n  exeStrategyActions(actions);\n\n  showErrorMsg(error);\n\n  return error;\n}\n\n/**\n * 处理请求成功后的错误\n * @param response - 请求的响应\n */\nexport function handleResponseError(response: AxiosResponse) {\n  const error: Service.RequestError = {\n    type: 'axios',\n    code: DEFAULT_REQUEST_ERROR_CODE,\n    msg: DEFAULT_REQUEST_ERROR_MSG\n  };\n\n  if (!window.navigator.onLine) {\n    // 网路错误\n    Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });\n  } else {\n    // 请求成功的状态码非200的错误\n    const errorCode: ErrorStatus = response.status as ErrorStatus;\n    const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG;\n    Object.assign(error, { type: 'http', code: errorCode, msg });\n  }\n\n  showErrorMsg(error);\n\n  return error;\n}\n\n/**\n * 处理后端返回的错误(业务错误)\n * @param backendResult - 后端接口的响应数据\n */\nexport function handleBackendError(backendResult: Record<string, any>, config: Service.BackendResultConfig) {\n  const { codeKey, msgKey } = config;\n  const error: Service.RequestError = {\n    type: 'backend',\n    code: backendResult[codeKey],\n    msg: backendResult[msgKey]\n  };\n\n  showErrorMsg(error);\n\n  return error;\n}\n"
  },
  {
    "path": "src/utils/service/handler.ts",
    "content": "/** 统一失败和成功的请求结果的数据类型 */\nexport async function handleServiceResult<T = any>(error: Service.RequestError | null, data: any) {\n  if (error) {\n    const fail: Service.FailedResult = {\n      error,\n      data: null\n    };\n    return fail;\n  }\n  const success: Service.SuccessResult<T> = {\n    error: null,\n    data\n  };\n  return success;\n}\n\n/** 请求结果的适配器：用于接收适配器函数和请求结果 */\nexport function adapter<T extends Service.ServiceAdapter>(\n  adapterFun: T,\n  ...args: Service.MultiRequestResult<TypeUtil.GetFunArgs<T>>\n): Service.RequestResult<ReturnType<T>> {\n  let result: Service.RequestResult | undefined;\n\n  const hasError = args.some(item => {\n    const flag = Boolean(item.error);\n    if (flag) {\n      result = {\n        error: item.error,\n        data: null\n      };\n    }\n    return flag;\n  });\n\n  if (!hasError) {\n    const adapterFunArgs = args.map(item => item.data);\n    result = {\n      error: null,\n      data: adapterFun(...adapterFunArgs)\n    };\n  }\n\n  return result!;\n}\n"
  },
  {
    "path": "src/utils/service/index.ts",
    "content": "export * from './transform';\nexport * from './error';\nexport * from './handler';\n"
  },
  {
    "path": "src/utils/service/msg.ts",
    "content": "import { ERROR_MSG_DURATION, NO_ERROR_MSG_CODE } from '@/configs/service';\n\n/** 错误消息栈，防止同一错误同时出现 */\nconst errorMsgStack = new Map<string | number, string>([]);\n\nfunction addErrorMsg(error: Service.RequestError) {\n  errorMsgStack.set(error.code, error.msg);\n}\nfunction removeErrorMsg(error: Service.RequestError) {\n  errorMsgStack.delete(error.code);\n}\nfunction hasErrorMsg(error: Service.RequestError) {\n  return errorMsgStack.has(error.code);\n}\n\n/**\n * 显示错误信息\n * @param error\n */\nexport function showErrorMsg(error: Service.RequestError) {\n  if (!error.msg || NO_ERROR_MSG_CODE.includes(error.code) || hasErrorMsg(error)) return;\n\n  addErrorMsg(error);\n  window.console.warn(error.code, error.msg);\n  window.$snackBar?.error(error.msg, {timeout: ERROR_MSG_DURATION });\n  setTimeout(() => {\n    removeErrorMsg(error);\n  }, ERROR_MSG_DURATION);\n}\n"
  },
  {
    "path": "src/utils/service/transform.ts",
    "content": "import qs from 'qs';\nimport FormData from 'form-data';\nimport { EnumContentType } from '@/enum';\nimport { isArray, isFile } from '../common';\n\n/**\n * 请求数据的转换\n * @param requestData - 请求数据\n * @param contentType - 请求头的Content-Type\n */\nexport async function transformRequestData(requestData: any, contentType?: string) {\n  // application/json类型不处理\n  let data = requestData;\n  // form类型转换\n  if (contentType === EnumContentType.formUrlencoded) {\n    data = qs.stringify(requestData);\n  }\n  // form-data类型转换\n  if (contentType === EnumContentType.formData) {\n    data = await handleFormData(requestData);\n  }\n\n  return data;\n}\n\nasync function handleFormData(data: Record<string, any>) {\n  const formData = new FormData();\n  const entries = Object.entries(data);\n\n  entries.forEach(async ([key, value]) => {\n    const isFileType = isFile(value) || (isArray(value) && value.length && isFile(value[0]));\n\n    if (isFileType) {\n      await transformFile(formData, key, value);\n    } else {\n      formData.append(key, value);\n    }\n  });\n\n  return formData;\n}\n\n/**\n * 接口为上传文件的类型时数据转换\n * @param key - 文件的属性名\n * @param file - 单文件或多文件\n */\nasync function transformFile(formData: FormData, key: string, file: File[] | File) {\n  if (isArray(file)) {\n    // 多文件\n    await Promise.all(\n      (file as File[]).map(item => {\n        formData.append(key, item);\n        return true;\n      })\n    );\n  } else {\n    // 单文件\n    formData.append(key, file);\n  }\n}\n"
  },
  {
    "path": "src/utils/storage/index.ts",
    "content": "export * from './local';\nexport * from './session';\n"
  },
  {
    "path": "src/utils/storage/local.ts",
    "content": "import { decrypto, encrypto } from '../crypto';\ninterface StorageData<T> {\n  value: T;\n  expire: number | null;\n}\n\nfunction createLocalStorage<T extends StorageInterface.Local = StorageInterface.Local>() {\n  const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;\n\n  function set<K extends keyof T>(key: K, value: T[K], expire: number | null = DEFAULT_CACHE_TIME) {\n    const storageData: StorageData<T[K]> = {\n      value,\n      expire: expire !== null ? new Date().getTime() + expire * 1000 : null\n    };\n    const json = encrypto(storageData);\n    window.localStorage.setItem(key as string, json);\n  }\n\n  function get<K extends keyof T>(key: K) {\n    const json = window.localStorage.getItem(key as string);\n    if (json) {\n      let storageData: StorageData<T[K]> | null = null;\n      try {\n        storageData = decrypto(json);\n      } catch {\n        // 防止解析失败\n      }\n      if (storageData) {\n        const { value, expire } = storageData;\n        // 在有效期内直接返回\n        if (expire === null || expire >= Date.now()) {\n          return value as T[K];\n        }\n      }\n      remove(key);\n      return null;\n    }\n    return null;\n  }\n\n  function remove(key: keyof T) {\n    window.localStorage.removeItem(key as string);\n  }\n  function clear() {\n    window.localStorage.clear();\n  }\n\n  return {\n    set,\n    get,\n    remove,\n    clear\n  };\n}\n\nexport const localStg = createLocalStorage();\n"
  },
  {
    "path": "src/utils/storage/session.ts",
    "content": "import { decrypto, encrypto } from '../crypto';\n\nexport function setSession(key: string, value: unknown) {\n  const json = encrypto(value);\n  sessionStorage.setItem(key, json);\n}\n\nexport function getSession<T>(key: string) {\n  const json = sessionStorage.getItem(key);\n  let data: T | null = null;\n  if (json) {\n    try {\n      data = decrypto(json);\n    } catch {\n      // 防止解析失败\n    }\n  }\n  return data;\n}\n\nexport function removeSession(key: string) {\n  window.sessionStorage.removeItem(key);\n}\n\nexport function clearSession() {\n  window.sessionStorage.clear();\n}\n\nfunction createSessionStorage<T extends StorageInterface.Session = StorageInterface.Session>() {\n  function set<K extends keyof T>(key: K, value: T[K]) {\n    const json = encrypto(value);\n    sessionStorage.setItem(key as string, json);\n  }\n  function get<K extends keyof T>(key: K) {\n    const json = sessionStorage.getItem(key as string);\n    let data: T[K] | null = null;\n    if (json) {\n      try {\n        data = decrypto(json);\n      } catch {\n        // 防止解析失败\n      }\n    }\n    return data;\n  }\n  function remove(key: keyof T) {\n    window.sessionStorage.removeItem(key as string);\n  }\n  function clear() {\n    window.sessionStorage.clear();\n  }\n\n  return {\n    set,\n    get,\n    remove,\n    clear\n  };\n}\n\nexport const sessionStg = createSessionStorage();\n"
  },
  {
    "path": "src/utils/vue/index.ts",
    "content": "import { createTextVNode, VNodeChild } from 'vue'\n\nexport const render = <T extends any[]>(\n  r:\n    | string\n    | number\n    | undefined\n    | null\n    | ((...args: [...T]) => VNodeChild)\n    | unknown,\n  ...args: [...T]\n): VNodeChild => {\n  if (typeof r === 'function') {\n    return r(...args)\n  } else if (typeof r === 'string') {\n    return createTextVNode(r)\n  } else if (typeof r === 'number') {\n    return createTextVNode(String(r))\n  } else {\n    return null\n  }\n}\n"
  },
  {
    "path": "src/views/_builtin/auth/components/ForgotPage.vue",
    "content": "<template>\n  <v-card class=\"text-center pa-1\">\n    <v-card-title class=\"justify-center text-h4 mb-2\">{{ $t('forgot.title') }}</v-card-title>\n    <v-card-subtitle>\n      {{ $t('forgot.subtitle') }}\n    </v-card-subtitle>\n\n    <!-- reset form -->\n    <v-card-text>\n      <v-form ref=\"form\" v-model=\"isFormValid\" @submit.prevent=\"submit\">\n        <v-text-field\n          v-model=\"email\"\n          :rules=\"[rules.required]\"\n          class=\"text-left\"\n          :error=\"error\"\n          :error-messages=\"errorMessages\"\n          :label=\"$t('forgot.email')\"\n          name=\"email\"\n          variant=\"outlined\"\n          @keyup.enter=\"submit\"\n          @change=\"resetErrors\"\n        ></v-text-field>\n\n        <v-btn\n          :loading=\"isLoading\"\n          block\n          size=\"x-large\"\n          color=\"primary\"\n          @click=\"submit\"\n        >{{ $t('forgot.button') }}\n        </v-btn>\n      </v-form>\n    </v-card-text>\n    <a class=\"text-decoration-underline text-center mt-6 cursor-pointer\" @click=\"toLoginModule('sign-in')\">\n      {{ $t('forgot.backtosign') }}\n    </a>\n  </v-card>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {useLoading} from \"@/hooks\";\nimport {useRouterPush} from '@/composables'\nimport {ref} from 'vue'\n\nconst {toLoginModule} = useRouterPush();\nconst {loading: isLoading, startLoading, endLoading} = useLoading()\nconst isFormValid = ref(false)\nconst email = ref('')\nconst error = ref(false)\nconst errorMessages = ref('')\nconst rules = ref(\n  {\n    required: (value: string) => !!value || 'Required'\n  }\n)\nconst form = ref<import('vuetify/components').VForm>()\n\nconst submit = () => {\n  form.value?.validate().then(r => {\n    if (r.valid) {\n      startLoading()\n      setTimeout(()=>{\n        endLoading()\n        window.$snackBar?.success(\"action success\")\n      },1000)\n    }\n  })\n}\nconst resetErrors = () => {\n  error.value = false\n  errorMessages.value = ''\n}\n</script>\n"
  },
  {
    "path": "src/views/_builtin/auth/components/ResetPage.vue",
    "content": "<template>\n  <v-card class=\"pa-2\">\n    <v-card-title class=\"justify-center text-h4 mb-2\">Set new password</v-card-title>\n    <div class=\"overline\">{{ status }}</div>\n    <div class=\"error--text mt-2 mb-4\">{{ error }}</div>\n\n    <a v-if=\"error\" href=\"/public\">Back to Sign In</a>\n\n    <v-text-field\n      v-model=\"newPassword\"\n      :append-icon=\"showPassword ? 'mdi-eye' : 'mdi-eye-off'\"\n      :rules=\"[rules.required]\"\n      :type=\"showPassword ? 'text' : 'password'\"\n      :error=\"errorNewPassword\"\n      :error-messages=\"errorNewPasswordMessage\"\n      variant=\"underlined\"\n      name=\"password\"\n      label=\"New Password\"\n      class=\"mt-4\"\n      @change=\"resetErrors\"\n      @keyup.enter=\"confirmPasswordReset\"\n      @click:append=\"showPassword = !showPassword\"\n    ></v-text-field>\n\n    <v-btn\n      :loading=\"isLoading\"\n      block\n      depressed\n      size=\"x-large\"\n      color=\"primary\"\n      @click=\"confirmPasswordReset\"\n    >Set new password and Sign In\n    </v-btn>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useLoading} from \"@/hooks\";\n\nconst {loading: isLoading, startLoading, endLoading} = useLoading()\nconst newPassword = ref('')\nconst errorNewPassword = ref(false)\nconst errorNewPasswordMessage = ref('')\nconst showPassword = ref(false)\nconst status = ref('Resetting password')\nconst error = ref()\nconst rules = ref({\n  required: (value: string) => (value && Boolean(value)) || 'Required'\n})\n\nconst confirmPasswordReset = () => {\n  startLoading()\n  setTimeout(() => {\n    endLoading()\n  }, 1000)\n}\n\nconst resetErrors = () => {\n  errorNewPassword.value = false\n  errorNewPasswordMessage.value = ''\n}\n</script>\n"
  },
  {
    "path": "src/views/_builtin/auth/components/SigninPage.vue",
    "content": "<template>\n  <div>\n    <v-card class=\"text-center pa-1\">\n      <v-card-title class=\"justify-center text-h4 mb-2  font-weight-regular\">Welcome</v-card-title>\n      <v-card-subtitle>Sign in to your account</v-card-subtitle>\n\n      <!-- sign in form -->\n      <v-card-text>\n        <v-form ref=\"form\" v-model=\"isFormValid\">\n          <v-text-field\n            v-model=\"email\"\n            class=\"text-left\"\n            :rules=\"[rules.required]\"\n            :validate-on-blur=\"false\"\n            :error=\"error\"\n            :label=\"$t('login.email')\"\n            variant=\"outlined\"\n            clearable\n            outlined\n            @keyup.enter=\"submit\"\n            @change=\"resetErrors\"\n          ></v-text-field>\n\n          <v-text-field\n            v-model=\"password\"\n            class=\"text-left\"\n            :append-inner-icon=\"showPassword ? 'mdi-eye' : 'mdi-eye-off'\"\n            :rules=\"[rules.required]\"\n            :type=\"showPassword ? 'text' : 'password'\"\n            :error=\"error\"\n            :error-messages=\"errorMessages\"\n            :label=\"$t('login.password')\"\n            name=\"password\"\n            variant=\"outlined\"\n            clearable\n            @change=\"resetErrors\"\n            @keyup.enter=\"submit\"\n            @click:appendInner=\"showPassword=!showPassword\"\n          ></v-text-field>\n\n\n          <v-btn\n            :loading=\"isLoading\"\n            :disabled=\"isLoading\"\n            block\n            size=\"x-large\"\n            color=\"primary\"\n            @click=\"submit\"\n          >{{ $t('login.button') }}\n          </v-btn>\n\n          <div class=\"text-caption font-weight-bold text-uppercase my-3\">{{ $t('login.orsign') }}</div>\n\n          <!-- external providers list -->\n          <v-btn\n            v-for=\"provider in providers\"\n            :key=\"provider.id\"\n            :loading=\"provider.isLoading\"\n            :disabled=\"isLoading\"\n            class=\"mb-2 bg-primary-lighten-2 lighten-2 text-primary-darken-3\"\n            block\n            size=\"x-large\"\n            to=\"/\"\n          >\n            <v-icon size=\"small\" start>mdi-{{ provider.id }}</v-icon>\n            {{ provider.label }}\n          </v-btn>\n\n          <div v-if=\"errorProvider\" class=\"error--text\">{{ errorProviderMessages }}</div>\n\n        </v-form>\n        <a class=\"mt-5 text-decoration-underline\" style=\"cursor: pointer\" @click=\"toLoginModule('forgot')\">\n          {{ $t('login.forgot') }}\n        </a>\n      </v-card-text>\n    </v-card>\n\n    <div class=\"text-center mt-6\">\n      {{ $t('login.noaccount') }}\n      <a @click=\"toLoginModule('sign-up')\" style=\"cursor: pointer\" class=\"font-weight-bold text-decoration-underline\">\n        {{ $t('login.create') }}\n      </a>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\n\nimport {ref} from 'vue'\nimport {useRouterPush} from \"@/composables\";\nimport {useAuthStore} from \"@/store\";\n\nconst auth = useAuthStore()\nconst {loginLoading: isLoading} = storeToRefs(auth)\nconst {login} = useAuthStore();\nconst {toLoginModule} = useRouterPush();\nconst form = ref<import('vuetify/components').VForm>()\nconst isFormValid = ref(false)\nconst email = ref('admin')\nconst password = ref('admin123')\nconst error = ref(false)\nconst errorMessages = ref('')\nconst errorProvider = ref(false)\nconst errorProviderMessages = ref('')\nconst showPassword = ref(false)\nconst providers = ref([{\n  id: 'google',\n  label: 'Google',\n  isLoading: false\n}, {\n  id: 'facebook',\n  label: 'Facebook',\n  isLoading: false\n}])\nconst rules = ref({\n  required: (value: boolean) => (value && Boolean(value)) || 'Required'\n})\n\nconst submit = () => {\n  form.value?.validate().then(async r => {\n    if (r.valid) {\n      await login(email.value, password.value)\n    }\n  })\n}\nconst resetErrors = () => {\n  error.value = false\n  errorMessages.value = \"\"\n  errorProvider.value = false\n  errorProviderMessages.value = \"\"\n}\n\n</script>\n"
  },
  {
    "path": "src/views/_builtin/auth/components/SignupPage.vue",
    "content": "<template>\n  <div>\n    <v-card class=\"text-center pa-1\">\n      <v-card-title class=\"justify-center text-h4 mb-2\">{{ $t('register.title') }}</v-card-title>\n      <v-card-subtitle>Let's build amazing products</v-card-subtitle>\n\n      <!-- sign up form -->\n      <v-card-text>\n        <v-form ref=\"form\" v-model=\"isFormValid\">\n          <v-text-field\n            v-model=\"name\"\n            :rules=\"[rules.required]\"\n            class=\"text-left\"\n            :error=\"errorName\"\n            :error-messages=\"errorNameMessage\"\n            :label=\"$t('register.name')\"\n            name=\"name\"\n            variant=\"outlined\"\n            @keyup.enter=\"submit\"\n            @change=\"resetErrors\"\n          ></v-text-field>\n\n          <v-text-field\n            v-model=\"email\"\n            :rules=\"[rules.required]\"\n            :error=\"errorEmail\"\n            class=\"text-left\"\n            :error-messages=\"errorEmailMessage\"\n            :label=\"$t('register.email')\"\n            name=\"email\"\n            variant=\"outlined\"\n            @keyup.enter=\"submit\"\n            @change=\"resetErrors\"\n          ></v-text-field>\n\n          <v-text-field\n            v-model=\"password\"\n            class=\"text-left\"\n            :append-inner-icon=\"showPassword ? 'mdi-eye' : 'mdi-eye-off'\"\n            :rules=\"[rules.required]\"\n            :type=\"showPassword ? 'text' : 'password'\"\n            :error=\"errorPassword\"\n            :error-messages=\"errorPasswordMessage\"\n            :label=\"$t('register.password')\"\n            name=\"password\"\n            variant=\"outlined\"\n            @change=\"resetErrors\"\n            @keyup.enter=\"submit\"\n            @click:appendInner=\"showPassword = !showPassword\"\n          ></v-text-field>\n\n          <v-btn\n            :loading=\"isLoading\"\n            :disabled=\"isSignUpDisabled\"\n            block\n            size=\"x-large\"\n            color=\"primary\"\n            @click=\"submit\"\n          >{{ $t('register.button') }}\n          </v-btn>\n\n          <div class=\"caption font-weight-bold text-uppercase my-3\">{{ $t('register.orsign') }}</div>\n\n          <!-- external providers list -->\n          <v-btn\n            v-for=\"provider in providers\"\n            :key=\"provider.id\"\n            :loading=\"provider.isLoading\"\n            :disabled=\"isSignUpDisabled\"\n            class=\"mb-2 primary lighten-2 primary--text text--darken-3\"\n            block\n            size=\"x-large\"\n            @click=\"signProvider(provider)\"\n          >\n            <v-icon small left>mdi-{{ provider.id }}</v-icon>\n            {{ provider.label }}\n          </v-btn>\n\n          <div v-if=\"errorProvider\" class=\"error--text\">{{ errorProviderMessages }}</div>\n\n          <div class=\"mt-5 overline\">\n            {{ $t('register.agree') }}\n            <br/>\n            <router-link to=\"\">{{ $t('common.tos') }}</router-link>\n            &\n            <router-link to=\"\">{{ $t('common.policy') }}</router-link>\n          </div>\n        </v-form>\n      </v-card-text>\n    </v-card>\n\n    <div class=\"text-center mt-6\">\n      {{ $t('register.account') }}\n      <a @click=\"toLoginModule('sign-in')\" class=\"font-weight-bold cursor-pointer\">\n        {{ $t('register.signin') }}\n      </a>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useLoading} from \"@/hooks\";\nimport {useRouterPush} from \"@/composables\";\n\nconst {toLoginModule} = useRouterPush();\nconst {loading: isLoading, startLoading} = useLoading()\nconst isSignUpDisabled = ref(false)\nconst isFormValid = ref(true)\nconst email = ref('')\nconst password = ref('')\nconst name = ref('')\nconst errorName = ref(false)\nconst errorEmail = ref(false)\nconst errorPassword = ref(false)\nconst errorNameMessage = ref('')\nconst errorEmailMessage = ref('')\nconst errorPasswordMessage = ref('')\nconst errorProvider = ref(false)\nconst errorProviderMessages = ref('')\nconst showPassword = ref(false)\n\ninterface provider {\n  id: string,\n  label: string,\n  isLoading: false\n}\n\nconst providers = ref<Array<provider>>([{\n  id: 'google',\n  label: 'Google',\n  isLoading: false\n}, {\n  id: 'facebook',\n  label: 'Facebook',\n  isLoading: false\n}])\n\nconst rules = ref(\n  {\n    required: (value: string) => (value && Boolean(value)) || 'Required'\n  })\n\nconst form = ref<import('vuetify/components').VForm>()\n\nconst submit = () => {\n  form.value?.validate().then(r => {\n    if (r.valid) {\n      startLoading()\n      isSignUpDisabled.value = true\n      signUp(email.value, password.value)\n    }\n  })\n}\nconst signUp = (email: string, password: string) => {\n\n}\nconst signProvider = (p: provider) => {\n}\n\nconst resetErrors = () => {\n\n  errorName.value = false\n  errorEmail.value = false\n  errorPassword.value = false\n  errorNameMessage.value = ''\n  errorEmailMessage.value = ''\n  errorPasswordMessage.value = ''\n\n  errorProvider.value = false\n  errorProviderMessages.value = ''\n}\n</script>\n"
  },
  {
    "path": "src/views/_builtin/auth/components/VerifyEmailPage.vue",
    "content": "<template>\n  <v-card class=\"pa-2\">\n    <h1>Please verify the email</h1>\n    <div class=\"mb-6 overline\">Please check your email for the link to verify the email.</div>\n\n    <v-btn\n      :loading=\"isLoading\"\n      :disabled=\"disabled\"\n      block\n      depressed\n      size=\"x-large\"\n      color=\"primary\"\n      @click=\"resend\"\n    >Re-send email {{ seconds }}\n    </v-btn>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useLoading} from \"@/hooks\";\n\nconst TIMEOUT = 10\n\nconst {loading: isLoading} = useLoading()\nconst disabled = ref(true)\nconst times = ref(0)\nconst resendInterval = ref<NodeJS.Timer>()\nconst secondsToEnable = ref(TIMEOUT)\nconst seconds = ref('')\nonMounted(() => {\n  setTimer()\n})\n\n\nconst setTimer = () => {\n  disabled.value = true\n  times.value++\n  secondsToEnable.value = TIMEOUT * times.value\n\n  resendInterval.value = setInterval(() => {\n    if (secondsToEnable.value === 0) {\n      clearInterval(resendInterval.value)\n      seconds.value = ''\n      disabled.value = false\n    } else {\n      seconds.value = `( ${secondsToEnable.value} )`\n      secondsToEnable.value--\n    }\n  }, 1000)\n}\n\n\nconst resend = async () => {\n  setTimer()\n}\n\n</script>\n"
  },
  {
    "path": "src/views/_builtin/auth/components/index.ts",
    "content": "import ForgotPg from './ForgotPage.vue';\nimport SigninPg from './SigninPage.vue';\nimport SignUpPg from './SignupPage.vue';\nimport ResetPg from './ResetPage.vue';\nimport VerifyEmailPg from './VerifyEmailPage.vue';\n\nexport {ForgotPg,SignUpPg,SigninPg,ResetPg,VerifyEmailPg};\n"
  },
  {
    "path": "src/views/_builtin/auth/index.vue",
    "content": "<template>\n  <transition name=\"fade-slide\" mode=\"out-in\" appear>\n    <component :is=\"activeModule.component\"/>\n  </transition>\n</template>\n\n<script lang=\"ts\" setup>\nimport type {Component} from 'vue';\nimport {ResetPg, SigninPg, SignUpPg, VerifyEmailPg, ForgotPg} from './components';\ninterface Props {\n  module: EnumType.LoginModuleKey;\n}\n\nconst props = defineProps<Props>();\n\n\ninterface LoginModule {\n  key: EnumType.LoginModuleKey;\n  component: Component;\n}\n\nconst modules: LoginModule[] = [\n  {key: 'forgot', component: ForgotPg},\n  {key: 'sign-in', component: SigninPg},\n  {key: 'sign-up', component: SignUpPg},\n  {key: 'reset', component: ResetPg},\n  {key: 'verify-email', component: VerifyEmailPg}\n];\n\nconst activeModule = computed(() => {\n  const active: LoginModule = {...modules[0]};\n  const findItem = modules.find(item => item.key === props.module);\n  if (findItem) {\n    Object.assign(active, findItem);\n  }\n  return active;\n});\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/_builtin/error/NotFoundPage.vue",
    "content": "<template>\n  <v-card class=\"text-center w-full error-page pa-3\">\n    <v-img src=\"/images/illustrations/404-illustration.svg\" max-height=\"400\" contain/>\n    <div class=\"text-h3 mt-10\">How did you get here?</div>\n    <div class=\"mt-3 mb-6\">Sorry we can't seem to find the page you're looking for.</div>\n    <v-text-field variant=\"solo\" placeholder=\"Search website\"></v-text-field>\n    <v-btn to=\"/dashboard\" block size=\"large\" color=\"primary\">Send me Back</v-btn>\n  </v-card>\n</template>\n\n<style>\n.error-page {\n  max-width: 500px;\n}\n</style>\n"
  },
  {
    "path": "src/views/_builtin/error/UnexpectedPage.vue",
    "content": "<template>\n  <v-card class=\"text-center w-full error-page pa-3\">\n    <v-img src=\"/images/illustrations/500-illustration.svg\" max-height=\"400\" contain/>\n    <div class=\"text-h3 mt-10\">OOPS! Something went wrong here</div>\n    <div class=\"mt-3 mb-10\">Our experts are working to fix the issue.</div>\n    <v-text-field variant=\"solo\" placeholder=\"Search website\"></v-text-field>\n    <v-btn to=\"/\" block size=\"large\" color=\"primary\">Send me back</v-btn>\n  </v-card>\n</template>\n\n<style>\n.error-page {\n  max-width: 500px;\n}\n</style>\n"
  },
  {
    "path": "src/views/board/components/BoardCard.vue",
    "content": "<template>\n  <v-card @click=\"$emit('edit')\">\n    <div class=\"font-weight-bold\">{{ card.title }}</div>\n    <div>{{ card.description }}</div>\n    <v-menu offset-y left>\n      <template v-slot:activator=\"{ props }\">\n        <v-btn icon variant=\"text\" size=\"small\"  class=\"board-item-menu\" v-bind=\"props\">\n          <v-icon color=\"grey darken-2\">mdi-dots-vertical</v-icon>\n        </v-btn>\n      </template>\n      <v-list>\n        <v-list-item @click=\"$emit('edit')\">\n          <v-list-item-title>{{ $t('board.editCard') }}</v-list-item-title>\n        </v-list-item>\n        <v-list-item @click=\"$emit('delete')\">\n          <v-list-item-title>{{ $t('board.deleteCard') }}</v-list-item-title>\n        </v-list-item>\n      </v-list>\n    </v-menu>\n  </v-card>\n</template>\n\n<script setup lang=\"ts\">\nimport {PropType} from \"vue\";\nimport type {card} from '../types'\n\ndefineProps({\n  card: {\n    type: Object as PropType<card>,\n    default: () => ({})\n  }\n})\n</script>\n\n<style scoped>\n.board-item-menu {\n  position: absolute;\n  top: 10px;\n  right: 2px;\n}\n\n.v-application--is-rtl .board-item-menu {\n  left: 2px;\n  right: auto;\n}\n</style>\n"
  },
  {
    "path": "src/views/board/pages/BoardPage.vue",
    "content": "<template>\n  <div class=\"d-flex flex-grow-1 mt-2\">\n    <div class=\"board d-flex flex-grow-1 flex-row\">\n\n      <!-- board column -->\n      <div v-for=\"column in columns\" :key=\"column.key\" class=\"board-column pa-2\">\n        <h5>{{ $t(`board.state.${column.key}`) }}</h5>\n        <div class=\"board-column-actions\">\n          <v-btn variant=\"plain\" color=\"primary\" @click=\"column.isAddVisible = !column.isAddVisible\">\n            <v-icon>mdi-plus</v-icon>\n          </v-btn>\n        </div>\n\n        <!-- add new card form -->\n        <v-card v-show=\"column.isAddVisible\" class=\"pa-2 my-2 overflow-visible\">\n          <v-text-field\n            v-model=\"column.addTitle\"\n            :label=\"$t('common.title')\"\n            :placeholder=\"$t('board.titlePlaceholder')\"\n            autofocus\n            @keyup.enter=\"_addCard(column)\"\n            @keyup.esc=\"column.isAddVisible = false\"\n          ></v-text-field>\n          <div class=\"d-flex flex-md-row flex-column\">\n            <v-btn class=\"flex-grow-1 ma-1\" small @click=\"column.isAddVisible = !column.isAddVisible\">\n              {{ $t('common.cancel') }}\n            </v-btn>\n            <v-btn class=\"flex-grow-1 ma-1\" small color=\"primary\" @click=\"_addCard(column)\">{{\n                $t('common.add')\n              }}\n            </v-btn>\n          </div>\n        </v-card>\n        <vue-draggable\n          itemKey=\"name\"\n          :list=\"column.cards\"\n          v-bind=\"dragOptions\"\n\n          class=\"board-group\"\n          group=\"cardsGroup\"\n          @change=\"column.callback\"\n        >\n          <template #item=\"{element:card}\">\n            <board-card\n              :card=\"card\"\n              class=\"board-item my-2 pa-2\"\n              @delete=\"showDelete(card)\"\n              @edit=\"showEdit(card)\"\n            />\n          </template>\n        </vue-draggable>\n      </div>\n\n      <!-- edit card dialog -->\n      <v-dialog v-model=\"editDialog\" width=\"600\">\n        <v-card>\n          <v-card-title class=\"pa-2\">\n            <span>{{ $t('board.editCard') }}</span>\n            <v-spacer></v-spacer>\n            <v-btn variant=\"plain\" icon @click=\"editDialog = false\">\n              <v-icon>mdi-close</v-icon>\n            </v-btn>\n          </v-card-title>\n\n          <v-divider></v-divider>\n\n          <div>\n            <v-text-field\n              v-model=\"title\"\n              class=\"px-2 py-1\"\n              variant=\"plain\"\n              :label=\"$t('common.title')\"\n              :placeholder=\"$t('common.title')\"\n              autofocus\n              hide-details\n              @keyup.enter=\"save\"\n            ></v-text-field>\n\n            <v-divider></v-divider>\n\n            <v-textarea\n              v-model=\"description\"\n              class=\"px-2 py-1\"\n              variant=\"plain\"\n              :label=\"$t('common.description')\"\n              :placeholder=\"$t('common.description')\"\n              hide-details\n            ></v-textarea>\n          </div>\n\n          <v-divider></v-divider>\n\n          <v-card-actions class=\"pa-2\">\n            <v-btn variant=\"outlined\" @click=\"editDialog = false\">{{ $t('common.cancel') }}</v-btn>\n            <v-spacer></v-spacer>\n            <v-btn color=\"primary\" variant=\"elevated\" @click=\"save\">{{ $t('common.save') }}</v-btn>\n          </v-card-actions>\n        </v-card>\n      </v-dialog>\n\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref, watch} from 'vue'\nimport VueDraggable from 'vuedraggable'\nimport BoardCard from '../components/BoardCard.vue'\nimport type {state, card} from '../types'\nimport {useI18n} from \"vue-i18n\";\n\nconst states = ref<Array<state>>(['TODO', 'INPROGRESS', 'TESTING', 'DONE'])\n\n\ninterface column {\n  key: state,\n  addTitle?: '',\n  cards: Array<card>,\n  isAddVisible: boolean,\n  callback: (evt: any) => void\n}\n\nconst cards = ref<Array<card>>([{\n  id: 1,\n  title: 'fix: 💭 channel label on chat theme',\n  description: 'we need it bolder',\n  order: 1,\n  state: 'TODO'\n}, {\n  id: 2,\n  title: 'feature: new emojis on board 🤘',\n  description: 'we need it for reasons 🤤',\n  order: 0,\n  state: 'TODO'\n}, {\n  id: 3,\n  title: 'feature: add stripe account on signup',\n  description: '',\n  order: 1,\n  state: 'TESTING'\n}, {\n  id: 4,\n  title: 'refactor: scroll 📜 directive on big pages',\n  description: 'remember to check the scroll',\n  order: 0,\n  state: 'INPROGRESS'\n}, {\n  id: 5,\n  title: 'feature: add big cards on dashboard',\n  description: 'everyone loves cards',\n  order: 3,\n  state: 'TODO'\n}])\nconst columns = ref<Array<column>>([])\nconst editDialog = ref(false)\nconst cardToEdit = ref<card>()\nconst title = ref<string | undefined>('')\nconst description = ref('')\nconst cardToDelete = ref<card>()\n\nconst dragOptions = reactive({\n  animation: 200,\n  group: 'description',\n  disabled: false,\n  ghostClass: 'ghost'\n})\nconst parse = (cards: Array<card>) => {\n  if (!cards) return columns.value.map((col) => (col.cards = []))\n  return columns.value.forEach((col) => {\n    col.cards = cards\n      .filter((card) => card.state === col.key)\n      .sort((a, b) => a.order < b.order ? -1 : 0)\n  })\n}\nconst _updateOrder = () => {\n  let tmp: Array<card> = []\n  columns.value.forEach((col) => {\n    tmp = [\n      ...tmp,\n      ...col.cards\n    ]\n  })\n  cards.value = tmp\n}\nconst changeState = (evt: any, colIndex: number) => {\n\n  if (evt.added || evt.moved) {\n    const column = columns.value[colIndex]\n    const state = column.key\n\n    for (let i = 0; i < column.cards.length; i++) {\n      column.cards[i].order = i\n      column.cards[i].state = state\n    }\n    _updateOrder()\n  }\n}\n\nwatch(cards, (n) => {\n  parse(n)\n}, {deep: true})\n\nonMounted(() => {\n  parse(cards.value)\n})\n\nonBeforeMount(() => {\n  states.value.forEach((state, index) => {\n    columns.value.push({\n      key: state,\n      cards: [],\n      isAddVisible: false,\n      callback: (evt) => changeState(evt, index)\n    })\n  })\n})\n\nconst showEdit = (card: card) => {\n  cardToEdit.value = card\n  title.value = card.title\n  description.value = card.description\n  editDialog.value = true\n}\nconst updateCard = (card: card) => {\n  const cardIdx = cards.value.find((t) => t.id === card.id)\n  if (cardIdx)\n    Object.assign(cardIdx, card)\n}\nconst showDelete = (card: card) => {\n  cardToDelete.value = card\n  showDeleteDialog(cardToDelete.value)\n}\nconst deleteCard = (card: card) => {\n  const cardIdx = cards.value.findIndex((t) => t.id === card.id)\n\n  if (cardIdx !== -1) cards.value.splice(cardIdx, 1)\n}\nconst save = () => {\n  if (cardToEdit.value) {\n    updateCard({\n      ...cardToEdit.value,\n      title: title.value,\n      description: description.value\n    })\n  }\n  editDialog.value = false\n}\nconst addCard = (card: card) => {\n  cards.value.push({\n    ...card,\n    id: '_' + Math.random().toString(36).substr(2, 9),\n  })\n}\nconst _addCard = async (column: column) => {\n\n  const {addTitle, key} = column\n\n  addCard({\n    state: key,\n    title: addTitle,\n    description: '',\n    order: -1,\n    id: -1\n  })\n\n  for (let i = 0; i < column.cards.length; i++) {\n    column.cards[i].order = i\n  }\n  await nextTick()\n  _updateOrder()\n  column.addTitle = ''\n}\n\n\nconst {t} = useI18n()\nconst showDeleteDialog = (card: card) => {\n  const dialog = window.$dialog?.show({\n    title: t('common.delete'),\n    main: t('board.deleteDescription'),\n    confirmText: t('common.delete'),\n    confirm: () => {\n      dialog?.confirmLoading(true)\n      setTimeout(() => {\n        dialog?.confirmLoading(false)\n        window.$snackBar?.info(`delete card '${card.title}' success`)\n        deleteCard(card)\n        dialog?.close()\n      }, 1000)\n    }\n  })\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.ghost {\n  opacity: 0.5;\n  background: var(--v-primary-lighten1) !important;\n}\n\n.board {\n  display: flex;\n  overflow-x: scroll;\n\n  .board-column {\n    position: relative;\n    display: flex;\n    flex: 1;\n    flex-direction: column;\n    min-width: 180px;\n\n    &-actions {\n      position: absolute;\n      top: 12px;\n      right: 6px;\n    }\n  }\n\n  .board-group {\n    min-height: 100%;\n  }\n\n  .board-item {\n    position: relative;\n    min-height: 54px;\n    transition: transform 0.2s;\n    cursor: pointer;\n\n    > a {\n      display: block;\n      padding: 8px;\n    }\n\n    &:hover {\n      transform: translateY(-6px);\n    }\n\n    &.sortable-chosen {\n      cursor: all-scroll;\n    }\n  }\n}\n\n.v-application--is-rtl {\n  .board-column-actions {\n    left: 6px;\n    right: auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/board/types.ts",
    "content": "export type state = 'TODO' | 'INPROGRESS' | 'TESTING' | 'DONE'\nexport interface card {\n  id: string | number,\n  title?: string,\n  description: string,\n  order: number,\n  state: state,\n}\n\n"
  },
  {
    "path": "src/views/chart/ChannelMessage.vue",
    "content": "<template>\n  <div class=\"d-flex flex-grow-1\" :class=\"{ 'flex-row-reverse': isOwnMessage}\">\n    <v-avatar size=\"40\" class=\"elevation-1 grey lighten-3\">\n      <svg-icon :name=\"message.user.avatar\"></svg-icon>\n    </v-avatar>\n\n    <div class=\"mx-2\">\n      <v-tooltip location=\"bottom\">\n        <template v-slot:activator=\"{ props }\">\n          <v-card\n            class=\"pa-1\"\n            :color=\"isOwnMessage?'primary-darken-1':undefined\"\n            v-bind=\"props\"\n          >\n            <div class=\"font-weight-bold\">{{ message.text }}</div>\n            <v-img class=\"mt-1 rounded\" v-if=\"message.image\" :src=\"message.image\" max-width=\"300\"\n                   min-width=\"125\">\n            </v-img>\n          </v-card>\n        </template>\n        <span>{{ message.timestamp }}</span>\n      </v-tooltip>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {PropType} from \"vue\";\nimport {useAuthStore} from \"@/store\";\n\nconst {userInfo} = useAuthStore()\n\nconst props = defineProps({\n  message: {\n    type: Object as PropType<ApiChatManagement.message>,\n    default: () => {\n    }\n  }\n})\n\n\nconst isOwnMessage = computed(() => {\n  return userInfo.userId === props.message.user.id\n})\n\n</script>\n"
  },
  {
    "path": "src/views/chart/ChatChannel.vue",
    "content": "<template>\n  <div>\n    <!-- channel toolbar -->\n    <v-toolbar flat height=\"64\" color=\"surface\">\n      <v-app-bar-nav-icon class=\"hidden-lg-and-up\" @click=\"$emit('toggle-menu')\"></v-app-bar-nav-icon>\n      <v-toolbar-title>\n        # {{ $route.params.id }}\n      </v-toolbar-title>\n      <v-spacer></v-spacer>\n      <v-btn class=\"mx-1\" icon @click.stop=\"$emit('toggle-usersDrawer')\">\n        <v-icon>mdi-account-group-outline</v-icon>\n      </v-btn>\n    </v-toolbar>\n\n    <v-divider></v-divider>\n    <!-- channel messages -->\n    <div class=\"channel-page\" :class=\"{'channel-page-bg':!current.dark}\">\n      <div id=\"messages\" ref=\"messagesRef\" class=\"messages mx-2\">\n        <v-slide-y-transition group tag=\"div\">\n          <channel-message\n            v-for=\"message in messages\"\n            :key=\"message.id\"\n            :message=\"message\"\n            class=\"my-4 d-flex\"\n          />\n        </v-slide-y-transition>\n      </div>\n\n      <div class=\"input-box pa-2\">\n        <div class=\"d-flex position-relative align-center\">\n          <v-text-field\n            v-model=\"input\"\n            variant=\"outlined\"\n            density=\"comfortable\"\n            ref=\"inputMessage\"\n            autofocus\n            class=\"font-weight-bold position-relative align-center\"\n            :placeholder=\"`${$t('chat.message')} #${$route.params.id}`\"\n            hide-details\n            @keyup.enter=\"sendMessage\"\n          >\n          </v-text-field>\n          <v-btn\n            flat\n            rounded\n            icon\n            size=\"small\"\n            color=\"primary\"\n            class=\"mx-1\"\n            :disabled=\"!input\"\n            @click=\"sendMessage\"\n          >\n            <v-icon size=\"small\">mdi-send</v-icon>\n          </v-btn>\n        </div>\n      </div>\n    </div>\n\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport ChannelMessage from './ChannelMessage.vue'\nimport {ref} from \"vue\";\nimport {fetchMessage} from '@/service'\nimport {useTheme} from 'vuetify'\nimport {useAuthStore} from \"@/store\";\nimport {createId} from \"seemly\";\n\n\nconst {current} = useTheme()\nconst {userInfo} = useAuthStore()\nconst messagesRef = ref<HTMLDivElement>()\nconst inputMessage = ref<HTMLDivElement>()\nconst messages = ref<Array<ApiChatManagement.message>>([\n  {\n    id: '1',\n    text: \"hi ,i am frank silva\",\n    timestamp: \"2022-12-13 15:13:10\",\n    user: {\n      avatar: 'avatar1',\n      id: '1'\n    }\n  },\n  {\n    id: '2',\n    text: \"who is lulu?\",\n    timestamp: \"2022-12-13 15:18:10\",\n    user: {\n      avatar: 'avatar3',\n      id: '2'\n    }\n  },\n  {\n    id: '3',\n    text: \"lulu is my arrogant cat\",\n    image: 'https://i.imgur.com/3e63lxL.jpg',\n    timestamp: \"2022-12-13 16:13:10\",\n    user: {\n      avatar: 'avatar9',\n      id: '3'\n    }\n  }\n])\ndefineEmits(['toggle-menu', 'toggle-usersDrawer'])\n\nconst scrollBottom = () => {\n  nextTick(() => {\n    if (messagesRef.value)\n      messagesRef.value.scrollTop = messagesRef.value.scrollHeight\n  })\n}\n\nconst input = ref('')\n\nconst demo = ref()\n\nonBeforeMount(() => {\n  demo.value = setInterval(async () => {\n    const resp = await fetchMessage()\n    if (resp.data) {\n      messages.value.push(resp.data)\n      scrollBottom()\n    }\n  }, 2000)\n})\n\nsetTimeout(() => {\n  demo.value && clearInterval(demo.value)\n}, 1000 * 20)\n\nonUnmounted(() => {\n  clearInterval(demo.value)\n})\n\n\nconst sendMessage = () => {\n  messages.value.push({\n    id: createId(),\n    text: input.value,\n    timestamp: new Date().toLocaleString(),\n    user: {\n      id: userInfo.userId,\n      avatar: userInfo.userAvatar ?? ''\n    }\n  })\n  input.value = \"\"\n  inputMessage.value?.focus()\n  scrollBottom()\n}\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n.channel-page-bg {\n  background: url(\"/images/chat/chat-bg-2.png\");\n}\n\n.channel-page {\n  position: absolute;\n  top: 65px;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  width: 100%;\n  display: flex;\n  flex-direction: column;\n\n  .messages {\n    flex-grow: 1;\n    margin-bottom: 68px;\n    overflow-y: scroll;\n    -webkit-overflow-scrolling: touch;\n    min-height: 0;\n  }\n\n  .input-box {\n    position: fixed;\n    bottom: 12px;\n    width: 100%;\n  }\n\n  .messages {\n    padding-bottom: 0;\n  }\n\n  .input-box {\n    position: absolute;\n    bottom: 12px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/chart/ChatPage.vue",
    "content": "<template>\n  <div class=\"h-100\">\n    <v-layout full-height :class=\"{'position-static':!lgAndUp}\">\n      <div class=\"d-flex flex-grow-1 flex-row\">\n        <v-navigation-drawer\n          v-model=\"channelDrawer\"\n          :permanent=\"lgAndUp\"\n          floating\n          class=\"elevation-1 rounded flex-shrink-0\"\n          :class=\"{'top-z-index':!lgAndUp}\"\n          width=\"240\"\n        >\n          <div class=\"px-2 py-1\">\n            <div class=\"title font-weight-bold text-primary\">Chat Template</div>\n            <div class=\"overline\">1.0.0</div>\n          </div>\n\n          <v-list density=\"comfortable\">\n\n            <v-list-subheader class=\"overline\">{{ $t('chat.channel') }}</v-list-subheader>\n            <div class=\"mx-2 mb-2\">\n              <v-btn variant=\"outlined\" block @click=\"showCreateDialog = true\">\n                <v-icon size=\"small\" start>mdi-plus</v-icon>\n                {{ $t('chat.addChannel') }}\n              </v-btn>\n            </div>\n\n            <v-list-item v-for=\"channelItem in channels\" :key=\"channelItem\" :to=\"`/apps/chat-channel/${channelItem}`\"\n                         exact>\n              <v-list-item-title class=\"text-subtitle-2\"># {{ channelItem }}</v-list-item-title>\n            </v-list-item>\n          </v-list>\n        </v-navigation-drawer>\n        <v-main>\n          <div :class=\"{'pl-3':lgAndUp}\" class=\"d-flex flex-grow-1 h-100 flex-column\">\n            <v-card class=\"flex-grow-1 h-100\">\n              <router-view :key=\"currentRoute.fullPath\"\n                           @toggle-users-drawer=\"usersDrawer=!usersDrawer\"\n                           @toggle-menu=\"channelDrawer= !channelDrawer\"></router-view>\n            </v-card>\n          </div>\n        </v-main>\n\n        <v-dialog v-model=\"showCreateDialog\" max-width=\"400\">\n          <v-card>\n            <v-card-title class=\"title\">{{ $t('chat.addChannel') }}</v-card-title>\n            <div class=\"pa-3\">\n              <v-text-field\n                ref=\"channel\"\n                v-model=\"newChannel\"\n                :label=\"$t('chat.channel')\"\n                maxlength=\"20\"\n                counter=\"20\"\n                autofocus\n                @keyup.enter=\"addChannel()\"\n              ></v-text-field>\n            </div>\n            <v-card-actions class=\"pa-2\">\n              <v-spacer></v-spacer>\n              <v-btn @click=\"showCreateDialog = false\">{{ $t('common.cancel') }}</v-btn>\n              <v-btn :loading=\"isLoadingAdd\" :disabled=\"newChannel.length===0\" color=\"success\" @click=\"addChannel()\">\n                {{ $t('common.add') }}\n              </v-btn>\n            </v-card-actions>\n          </v-card>\n        </v-dialog>\n      </div>\n\n    </v-layout>\n    <v-navigation-drawer\n      v-model=\"usersDrawer\"\n      width=\"180\"\n      order=\"-1\"\n      location=\"right\"\n    >\n      <v-list v-if=\"!fetchOnlineLoading\" dense>\n        <v-list-item-subtitle class=\"mx-2 my-2 overline \">\n          {{ $t('chat.online', {count: onlineUsers.length}) }}\n        </v-list-item-subtitle>\n        <v-list-item v-for=\"item in onlineUsers\" :key=\"item.id\">\n          <v-list-item-title class=\"text-body-2\" :class=\"{ 'text-primary': item.id ===userInfo.userId}\">\n            <v-avatar size=\"40\" class=\"elevation-1 grey lighten-3\">\n              <svg-icon :name=\"item.avatar\"></svg-icon>\n            </v-avatar>\n            {{ item.name }}\n          </v-list-item-title>\n        </v-list-item>\n      </v-list>\n      <v-row\n        v-else\n        class=\"fill-height\"\n        align-content=\"center\"\n        justify=\"center\"\n      >\n        <v-col\n          class=\"text-subtitle-1 text-center\"\n          cols=\"12\"\n        >\n          Fetching Online Users\n        </v-col>\n        <v-col cols=\"6\">\n          <v-progress-linear\n            color=\"primary\"\n            indeterminate\n            rounded\n            height=\"6\"\n          ></v-progress-linear>\n        </v-col>\n      </v-row>\n    </v-navigation-drawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useDisplay} from \"vuetify\";\nimport {ref} from 'vue'\nimport {useLoading} from \"@/hooks\";\nimport {useRouter} from \"vue-router\";\nimport {fetchUserList} from \"@/service\";\nimport {useRouterPush} from \"@/composables\";\n\nconst {loading: isLoadingAdd, startLoading, endLoading} = useLoading()\n\nconst channelDrawer = ref()\nconst showCreateDialog = ref(false)\nconst channels = ref(['general', 'production', 'qa', 'staging', 'random'])\nconst newChannel = ref(\"\")\nconst {routerPush} = useRouterPush()\nconst addChannel = () => {\n  startLoading()\n  setTimeout(() => {\n    channels.value.push(newChannel.value)\n    showCreateDialog.value = false\n    routerPush(`/apps/chat-channel/${newChannel.value}`)\n    newChannel.value = ''\n    endLoading()\n  }, 1000)\n}\nconst {lgAndUp} = useDisplay()\nconst auth = useAuthStore();\nconst {userInfo} = storeToRefs(auth)\nconst {currentRoute} = useRouter()\n\nconst usersDrawer = ref()\nconst onlineUsers = ref<Array<ApiUserManagement.User>>([])\n\nconst {loading: fetchOnlineLoading, startLoading: startFetchOnline, endLoading: endFetchOnline} = useLoading()\nconst fetchOnlineUsers = async () => {\n  startFetchOnline()\n  const resp = await fetchUserList();\n  endFetchOnline()\n  if (resp.data) {\n    onlineUsers.value = resp.data.list\n  }\n}\nonMounted(() => {\n  routerPush(`/apps/chat-channel/${channels.value[0]}`)\n  fetchOnlineUsers()\n\n})\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/dashboard/index.vue",
    "content": "<template>\n  <div class=\"d-flex flex-grow-1 flex-column\">\n    <v-row class=\"flex-grow-0 my-0\" dense>\n      <v-col cols=\"12\" xl=\"4\">\n        <sales-card\n          :value=\"1837.32\"\n          class=\"h-100\"\n          :percentage=\"3.2\"\n          style=\"min-height: 380px\"\n          :loading=\"isLoading.isLoading1\"\n          :percentage-label=\"$t('dashboard.lastweek')\"\n          :action-label=\"$t('dashboard.viewReport')\"\n        ></sales-card>\n      </v-col>\n      <v-col cols=\"12\" md=\"6\" xl=\"4\">\n        <activity-card\n          class=\"h-100\"\n        />\n      </v-col>\n\n      <v-col cols=\"12\" md=\"6\" xl=\"4\">\n        <sources-card\n          :label=\"$t('dashboard.sources')\"\n          class=\"h-100\"\n          color=\"#8c9eff\"\n          :value=\"432\"\n          :percentage=\"4.3\"\n          style=\"min-height: 380px\"\n          :loading=\"isLoading.isLoading2\"\n          :percentage-label=\"$t('dashboard.lastweek')\"\n          :series=\"[44, 55, 41, 17]\"\n        ></sources-card>\n      </v-col>\n    </v-row>\n\n    <v-row class=\"flex-grow-0 my-0\" dense>\n      <v-col cols=\"12\" lg=\"6\">\n        <table-card class=\"h-100\" :label=\"$t('dashboard.recentOrders')\"/>\n      </v-col>\n      <v-col cols=\"12\" lg=\"6\">\n        <div class=\"d-flex flex-column flex-grow-1 h-100\">\n          <track-card\n            :label=\"$t('dashboard.orders')\"\n            class=\"h-100\"\n            color=\"#8c9eff\"\n            :value=\"432\"\n            :percentage=\"4.3\"\n            :percentage-label=\"$t('dashboard.lastweek')\"\n            :loading=\"isLoading.loading3\"\n            :series=\"ordersSeries\"\n          ></track-card>\n          <track-card\n            :label=\"$t('dashboard.customers')\"\n            class=\"h-100 mt-2\"\n            :color=\"current.colors.success\"\n            :value=\"178\"\n            :percentage=\"2.12\"\n            :percentage-label=\"$t('dashboard.lastweek')\"\n            :loading=\"isLoading.loading3\"\n            :series=\"customersSeries\"\n          ></track-card>\n        </div>\n      </v-col>\n    </v-row>\n\n    <v-row class=\"flex-grow-0 my-0\" dense>\n      <v-col cols=\"12\" xl=\"6\">\n        <todo-card/>\n      </v-col>\n    </v-row>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst {current} = useTheme()\n\nconst isLoading: Record<string, boolean> = reactive({\n  isLoading1: true,\n  isLoading2: true,\n  isLoading3: true,\n  isLoading4: true,\n  isLoading5: true,\n})\nlet loadingInterval: NodeJS.Timeout\nlet count = 0\n\nonMounted(() => {\n  loadingInterval = setInterval(() => {\n    isLoading[`isLoading${count++}`] = false\n    if (count === 4) clearInterval(loadingInterval)\n  }, 400)\n})\n\nonBeforeUnmount(\n  () => clearInterval(loadingInterval)\n)\n\n\nconst ordersSeries = reactive([{\n  name: 'Orders',\n  data: [\n    ['2020-02-02', 34],\n    ['2020-02-03', 43],\n    ['2020-02-04', 40],\n    ['2020-02-05', 43]\n  ]\n}])\nconst customersSeries = reactive([{\n  name: 'Customers',\n  data: [\n    ['2020-02-02', 13],\n    ['2020-02-03', 11],\n    ['2020-02-04', 13],\n    ['2020-02-05', 12]\n  ]\n}])\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/bo-utils/conditionalUtil.ts",
    "content": "// helper ////////////////////\n\nimport {getBusinessObject, is, isAny} from \"bpmn-js/lib/util/ModelUtil\";\nimport {find} from \"lodash-es\";\n\nconst CONDITIONAL_SOURCES = [\n  'bpmn:Activity',\n  'bpmn:ExclusiveGateway',\n  'bpmn:InclusiveGateway',\n  'bpmn:ComplexGateway'\n];\n\n\nexport function isNotConditional(element: any) {\n  return (\n    !(is(element, 'bpmn:SequenceFlow') && isConditionalSource(element.source)) &&\n    !getConditionalEventDefinition(element)\n  )\n}\n\nfunction isConditionalSource(element: any) {\n  return isAny(element, CONDITIONAL_SOURCES);\n}\n\nfunction getConditionalEventDefinition(element: any) {\n  if (!is(element, 'bpmn:Event')) {\n    return false;\n  }\n\n  return getEventDefinition(element, 'bpmn:ConditionalEventDefinition');\n}\n\nfunction getEventDefinition(element: any, eventType: string) {\n  const businessObject = getBusinessObject(element);\n\n  const eventDefinitions = businessObject.get('eventDefinitions') || [];\n\n  return find(eventDefinitions, function (definition) {\n    return is(definition, eventType);\n  });\n}\n"
  },
  {
    "path": "src/views/flowable/bo-utils/documentationUtil.ts",
    "content": "import {Base} from 'diagram-js/lib/model'\nimport {useModelStore} from '@/store'\nimport {without} from 'min-dash'\n\nexport function getDocumentValue(element: Base): string {\n  const businessObject = element?.businessObject\n  const documentation = businessObject && findDocumentation(businessObject.get('documentation'))\n  return documentation && documentation.text\n}\n\nexport function setDocumentValue(element: Base, value: string | undefined) {\n  const store = useModelStore()\n\n  const modeling = store.getModeling\n  const bpmnFactory = store.getModeler?.get('bpmnFactory')\n\n  const businessObject = element.businessObject\n  const documentation = findDocumentation(businessObject && businessObject.get('documentation'))\n  // (1) 更新或者移除 原有 documentation\n  if (documentation) {\n    if (value) {\n      return modeling.updateModdleProperties(element, documentation, {text: value})\n    } else {\n      return modeling.updateModdleProperties(element, businessObject, {\n        documentation: without(businessObject.get('documentation'), documentation)\n      })\n    }\n  }\n  // (2) 创建新的 documentation\n  if (value) {\n    const newDocumentation = bpmnFactory?.create('bpmn:Documentation', {\n      text: value\n    })\n    return modeling.updateModdleProperties(element, businessObject, {\n      documentation: [...businessObject.get('documentation'), newDocumentation]\n    })\n  }\n}\n\n//////////// helpers\n\nconst DOCUMENTATION_TEXT_FORMAT = 'text/plain'\n\nfunction findDocumentation(docs: any) {\n  return docs.find(function (d: any) {\n    return (d.textFormat || DOCUMENTATION_TEXT_FORMAT) === DOCUMENTATION_TEXT_FORMAT\n  })\n}\n"
  },
  {
    "path": "src/views/flowable/bo-utils/idUtil.ts",
    "content": "import {Base} from 'diagram-js/lib/model'\nimport {useModelStore} from '@/store'\nimport {isIdValid} from \"@/views/flowable/utils/BpmnValidator\";\n\nexport function getIdValue(element: Base): string {\n  return element.businessObject.id\n}\n\nexport function setIdValue(element: Base, value: string) {\n  const errorMsg = isIdValid(element.businessObject, value)\n\n  if (errorMsg && errorMsg.length) {\n    return window.$snackBar?.warning(errorMsg)\n  }\n\n  const store = useModelStore()\n  const modeling = store.getModeling\n\n  modeling.updateProperties(element, {\n    id: value\n  })\n}\n"
  },
  {
    "path": "src/views/flowable/bo-utils/nameUtil.ts",
    "content": "import {useModelStore} from '@/store'\nimport {Base} from 'diagram-js/lib/model'\nimport {getBusinessObject, is} from 'bpmn-js/lib/util/ModelUtil'\nimport {isAny} from 'bpmn-js/lib/features/modeling/util/ModelingUtil'\nimport {add as collectionAdd} from 'diagram-js/lib/util/Collections'\n\n// 根据元素获取 name 属性\nexport function getNameValue(element: Base): string | undefined {\n  if (isAny(element, ['bpmn:Collaboration', 'bpmn:DataAssociation', 'bpmn:Association'])) {\n    return undefined\n  }\n  if (is(element, 'bpmn:TextAnnotation')) {\n    return element.businessObject.text\n  }\n  if (is(element, 'bpmn:Group')) {\n    const businessObject = getBusinessObject(element),\n      categoryValueRef = businessObject?.categoryValueRef\n    return categoryValueRef?.value\n  }\n  return element?.businessObject.name\n}\n\n// 根据输入内容设置 name 属性\nexport function setNameValue(element: Base, value: string): void {\n  const store = useModelStore()\n\n  const modeling = store.getModeling\n  const canvas = store.getCanvas\n  const bpmnFactory = store.getModeler?.get('bpmnFactory')\n\n  if (isAny(element, ['bpmn:Collaboration', 'bpmn:DataAssociation', 'bpmn:Association'])) {\n    return undefined\n  }\n\n  if (is(element, 'bpmn:TextAnnotation')) {\n    return modeling?.updateProperties(element, {text: value})\n  }\n  if (is(element, 'bpmn:Group')) {\n    const businessObject = getBusinessObject(element),\n      categoryValueRef = businessObject.categoryValueRef\n    if (!categoryValueRef) {\n      initializeCategory(businessObject, canvas?.getRootElement(), bpmnFactory)\n    }\n    return modeling?.updateLabel(element, value)\n  }\n  modeling?.updateProperties(element, {name: value})\n}\n\n////////////////  helpers\n\nfunction createCategoryValue(definitions: any, bpmnFactory: any): any {\n  const categoryValue = bpmnFactory.create('bpmn:CategoryValue')\n  const category = bpmnFactory.create('bpmn:Category', {\n    categoryValue: [categoryValue]\n  })\n  collectionAdd(definitions.get('rootElements'), category)\n  getBusinessObject(category).$parent = definitions\n  getBusinessObject(categoryValue).$parent = category\n\n  return categoryValue\n}\n\nfunction initializeCategory(businessObject: any, rootElement: any, bpmnFactory: any) {\n  const definitions = getBusinessObject(rootElement).$parent\n\n  businessObject.categoryValueRef = createCategoryValue(definitions, bpmnFactory)\n}\n"
  },
  {
    "path": "src/views/flowable/bo-utils/processUtil.ts",
    "content": "import {Base} from 'diagram-js/lib/model'\nimport {useModelStore} from '@/store'\n\nconst prefix = \"camunda\"\n\nexport function getProcessExecutable(element: Base): boolean {\n  return element.businessObject.isExecutable\n}\n\nexport function setProcessExecutable(element: Base, value: boolean) {\n  const store = useModelStore()\n  const modeling = store.getModeling\n\n  modeling.updateProperties(element, {\n    isExecutable: value\n  })\n}\n\nexport function getProcessVersionTag(element: Base): string | undefined {\n\n  return element.businessObject.get(`${prefix}:versionTag`)\n}\n\nexport function setProcessVersionTag(element: Base, value: string) {\n\n  const modeling = useModelStore().getModeling\n\n  modeling.updateProperties(element, {\n    [`${prefix}:versionTag`]: value\n  })\n}\n"
  },
  {
    "path": "src/views/flowable/bo-utils/userTaskUtil.ts",
    "content": "import {is} from \"bpmn-js/lib/util/ModelUtil\";\n\nexport function isUserService(element: any): boolean {\n  return is(element, 'bpmn:UserTask')\n}\n\nexport function isStartEvent(element: any): boolean {\n  return is(element, 'bpmn:StartEvent')\n}\n"
  },
  {
    "path": "src/views/flowable/design/customTranslate.ts",
    "content": "import translations from './translations';\n\n\nexport default function customTranslate(template: any, replacements: any) {\n  replacements = replacements || {};\n\n  // Translate\n  template = translations[template] || template;\n\n  // Replace\n  return template.replace(/{([^}]+)}/g, function (_: any, key: any) {\n    return replacements[key] || '{' + key + '}';\n  });\n}\n"
  },
  {
    "path": "src/views/flowable/design/demo3.bpmn",
    "content": "<bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:camunda=\"http://camunda.org/schema/1.0/bpmn\" xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\" xmlns:modeler=\"http://camunda.org/schema/modeler/1.0\" id=\"Definitions_0t9s4md\" targetNamespace=\"http://bpmn.io/schema/bpmn\" exporter=\"Camunda Modeler\" exporterVersion=\"5.4.2\" modeler:executionPlatform=\"Camunda Platform\" modeler:executionPlatformVersion=\"7.18.0\">\n  <bpmn:process id=\"Process_custom4\" isExecutable=\"true\">\n    <bpmn:extensionElements />\n    <bpmn:startEvent id=\"StartEvent_1\">\n      <bpmn:extensionElements>\n        <camunda:formData>\n          <camunda:formField id=\"user\" label=\"user\" type=\"userFormType\">\n            <camunda:properties>\n              <camunda:property id=\"required\" value=\"true\" />\n            </camunda:properties>\n            <camunda:validation>\n              <camunda:constraint name=\"required\" />\n            </camunda:validation>\n          </camunda:formField>\n          <camunda:formField id=\"file\" label=\"file\" />\n        </camunda:formData>\n      </bpmn:extensionElements>\n      <bpmn:outgoing>Flow_0kydfqm</bpmn:outgoing>\n    </bpmn:startEvent>\n    <bpmn:sequenceFlow id=\"Flow_0kydfqm\" sourceRef=\"StartEvent_1\" targetRef=\"Activity_1qhokjb\" />\n    <bpmn:userTask id=\"Activity_1qhokjb\" name=\"submit\" camunda:candidateUsers=\"admin\" camunda:candidateGroups=\"camunda-admin\">\n      <bpmn:extensionElements>\n        <camunda:formData>\n          <camunda:formField id=\"hello\" label=\"hello\" type=\"string\">\n            <camunda:properties>\n              <camunda:property id=\"uu\" value=\"22\" />\n            </camunda:properties>\n          </camunda:formField>\n        </camunda:formData>\n        <camunda:properties>\n          <camunda:property name=\"required\" value=\"true\" />\n        </camunda:properties>\n      </bpmn:extensionElements>\n      <bpmn:incoming>Flow_0kydfqm</bpmn:incoming>\n      <bpmn:outgoing>Flow_0krvqew</bpmn:outgoing>\n      <bpmn:outgoing>Flow_162i7sq</bpmn:outgoing>\n    </bpmn:userTask>\n    <bpmn:sequenceFlow id=\"Flow_0krvqew\" sourceRef=\"Activity_1qhokjb\" targetRef=\"Activity_09jm422\" />\n    <bpmn:userTask id=\"Activity_09jm422\" name=\"review\">\n      <bpmn:extensionElements>\n        <camunda:formData>\n          <camunda:formField id=\"review\" label=\"review\" type=\"boolean\" defaultValue=\"false\" />\n        </camunda:formData>\n      </bpmn:extensionElements>\n      <bpmn:incoming>Flow_0krvqew</bpmn:incoming>\n      <bpmn:outgoing>Flow_1bhkt0w</bpmn:outgoing>\n    </bpmn:userTask>\n    <bpmn:userTask id=\"Activity_0u6a0eo\" name=\"bossreview\">\n      <bpmn:extensionElements>\n        <camunda:formData>\n          <camunda:formField id=\"opinion\" label=\"opinion\" type=\"boolean\" defaultValue=\"false\">\n            <camunda:properties />\n            <camunda:validation />\n          </camunda:formField>\n          <camunda:formField id=\"date\" label=\"date\" type=\"date\" />\n        </camunda:formData>\n      </bpmn:extensionElements>\n      <bpmn:incoming>Flow_162i7sq</bpmn:incoming>\n      <bpmn:outgoing>Flow_13lqqfh</bpmn:outgoing>\n    </bpmn:userTask>\n    <bpmn:sequenceFlow id=\"Flow_162i7sq\" sourceRef=\"Activity_1qhokjb\" targetRef=\"Activity_0u6a0eo\" />\n    <bpmn:endEvent id=\"Event_0htpw86\">\n      <bpmn:incoming>Flow_13lqqfh</bpmn:incoming>\n      <bpmn:incoming>Flow_1bhkt0w</bpmn:incoming>\n    </bpmn:endEvent>\n    <bpmn:sequenceFlow id=\"Flow_13lqqfh\" sourceRef=\"Activity_0u6a0eo\" targetRef=\"Event_0htpw86\" />\n    <bpmn:sequenceFlow id=\"Flow_1bhkt0w\" sourceRef=\"Activity_09jm422\" targetRef=\"Event_0htpw86\" />\n  </bpmn:process>\n  <bpmndi:BPMNDiagram id=\"BPMNDiagram_1\">\n    <bpmndi:BPMNPlane id=\"BPMNPlane_1\" bpmnElement=\"Process_custom4\">\n      <bpmndi:BPMNShape id=\"_BPMNShape_StartEvent_2\" bpmnElement=\"StartEvent_1\">\n        <dc:Bounds x=\"152\" y=\"102\" width=\"36\" height=\"36\" />\n      </bpmndi:BPMNShape>\n      <bpmndi:BPMNShape id=\"Activity_0rt2b3u_di\" bpmnElement=\"Activity_1qhokjb\">\n        <dc:Bounds x=\"240\" y=\"80\" width=\"100\" height=\"80\" />\n        <bpmndi:BPMNLabel />\n      </bpmndi:BPMNShape>\n      <bpmndi:BPMNShape id=\"Activity_040yloe_di\" bpmnElement=\"Activity_09jm422\">\n        <dc:Bounds x=\"430\" y=\"80\" width=\"100\" height=\"80\" />\n      </bpmndi:BPMNShape>\n      <bpmndi:BPMNShape id=\"Activity_07aufj9_di\" bpmnElement=\"Activity_0u6a0eo\">\n        <dc:Bounds x=\"430\" y=\"220\" width=\"100\" height=\"80\" />\n        <bpmndi:BPMNLabel />\n      </bpmndi:BPMNShape>\n      <bpmndi:BPMNShape id=\"Event_0htpw86_di\" bpmnElement=\"Event_0htpw86\">\n        <dc:Bounds x=\"622\" y=\"242\" width=\"36\" height=\"36\" />\n      </bpmndi:BPMNShape>\n      <bpmndi:BPMNEdge id=\"Flow_0kydfqm_di\" bpmnElement=\"Flow_0kydfqm\">\n        <di:waypoint x=\"188\" y=\"120\" />\n        <di:waypoint x=\"240\" y=\"120\" />\n      </bpmndi:BPMNEdge>\n      <bpmndi:BPMNEdge id=\"Flow_0krvqew_di\" bpmnElement=\"Flow_0krvqew\">\n        <di:waypoint x=\"340\" y=\"120\" />\n        <di:waypoint x=\"430\" y=\"120\" />\n      </bpmndi:BPMNEdge>\n      <bpmndi:BPMNEdge id=\"Flow_162i7sq_di\" bpmnElement=\"Flow_162i7sq\">\n        <di:waypoint x=\"290\" y=\"160\" />\n        <di:waypoint x=\"290\" y=\"260\" />\n        <di:waypoint x=\"430\" y=\"260\" />\n      </bpmndi:BPMNEdge>\n      <bpmndi:BPMNEdge id=\"Flow_13lqqfh_di\" bpmnElement=\"Flow_13lqqfh\">\n        <di:waypoint x=\"530\" y=\"260\" />\n        <di:waypoint x=\"622\" y=\"260\" />\n      </bpmndi:BPMNEdge>\n      <bpmndi:BPMNEdge id=\"Flow_1bhkt0w_di\" bpmnElement=\"Flow_1bhkt0w\">\n        <di:waypoint x=\"530\" y=\"120\" />\n        <di:waypoint x=\"576\" y=\"120\" />\n        <di:waypoint x=\"576\" y=\"260\" />\n        <di:waypoint x=\"622\" y=\"260\" />\n      </bpmndi:BPMNEdge>\n    </bpmndi:BPMNPlane>\n  </bpmndi:BPMNDiagram>\n</bpmn:definitions>\n"
  },
  {
    "path": "src/views/flowable/design/design.tsx",
    "content": "import {defineComponent, ref, toRefs, nextTick} from 'vue'\nimport type {PropType} from 'vue'\nimport {useModelStore} from '@/store'\nimport customTranslate from \"./customTranslate\";\nimport initModeler from './initModeler'\nimport camundaModdleDescriptors from 'camunda-bpmn-moddle/resources/camunda';\n\nconst Designer = defineComponent({\n  name: 'Designer',\n  props: {\n    xml: {\n      type: String as PropType<string>,\n      default: undefined\n    }\n  },\n  emits: ['update:xml', 'command-stack-changed'],\n  setup(props, {emit, slots}) {\n    const {xml} = toRefs(props)\n    const designer = ref<HTMLDivElement | null>(null)\n    const {current} = useTheme()\n    onMounted(async () => {\n      try {\n        await nextTick()\n        const editorSettings = {\n          container: designer.value,\n          bpmnRenderer: {\n            defaultFillColor: current.value.colors.primary,\n            defaultLabelColor: current.value.colors.surface\n          },\n          keyboard: {\n            bindTo: document,\n          },\n          additionalModules: [\n            {\n              translate: ['value', customTranslate]\n            },\n          ],\n          moddleExtensions: {\n            camunda: camundaModdleDescriptors\n          }\n        }\n        initModeler(editorSettings, emit)\n        await createNewDiagram(xml.value, editorSettings)\n      } catch (e) {\n        console.log(e)\n      }\n    })\n\n    return () => <div ref={designer} class=\"canvas rounded\" id=\"js-canvas\">{slots.right?.()}</div>\n  }\n})\n\nexport default Designer\n\nconst createNewDiagram = async function (newXml?: string, settings?: any) {\n  try {\n    const store = useModelStore()\n    const timestamp = Date.now()\n    const {processId, processName, processEngine} = settings || {}\n    const newId: string = processId ? processId : `Process_${timestamp}`\n    const newName: string = processName || `Process_${timestamp}`\n    const xmlString = newXml || emptyXML(newId, newName, processEngine)\n    const modeler = store.getModeler\n    const {warnings} = await modeler!.importXML(xmlString)\n    if (warnings && warnings.length) {\n      warnings.forEach((warn: any) => console.warn(warn))\n    }\n  } catch (e) {\n    console.error(`[Process Designer Warn]: ${typeof e === 'string' ? e : (e as Error)?.message}`)\n  }\n}\n\nconst emptyXML = (key: string, name: string, type?: string): string => {\n  return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bpmn:definitions\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n  xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n  xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\"\n  xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\"\n  targetNamespace=\"http://bpmn.io/schema/bpmn\"\n  id=\"Definitions_${key}\">\n  <bpmn:process id=\"${key}\" name=\"${name}\" isExecutable=\"true\"></bpmn:process>\n  <bpmndi:BPMNDiagram id=\"BPMNDiagram_1\">\n    <bpmndi:BPMNPlane id=\"BPMNPlane_1\" bpmnElement=\"${key}\"></bpmndi:BPMNPlane>\n  </bpmndi:BPMNDiagram>\n</bpmn:definitions>`\n}\n"
  },
  {
    "path": "src/views/flowable/design/index.vue",
    "content": "<template>\n  <div class=\"flex-grow-1 h-100 d-flex flex-column\">\n    <v-toolbar rounded flat elevation=\"1\" color=\"surface\" class=\"mb-2\">\n      <v-toolbar-title class=\"text-primary font-weight-bold\">Flow Design</v-toolbar-title>\n      <v-spacer/>\n      <v-btn\n        variant=\"outlined\"\n        size=\"small\"\n        color=\"primary\"\n        :disabled=\"!changed\"\n        prepend-icon=\"mdi-download\"\n        @click=\"downloadSvg?.click()\"\n      >\n        SVG\n      </v-btn>\n      <a ref=\"downloadSvg\" class=\"d-none\" href=\"#\"></a>\n      <v-btn\n        variant=\"outlined\"\n        size=\"small\"\n        class=\"ml-1\"\n        :disabled=\"!changed\"\n        prepend-icon=\"mdi-download\"\n        @click=\"downloadDiagram?.click()\"\n        color=\"primary\"\n      >\n        Diagram\n      </v-btn>\n      <a ref=\"downloadDiagram\" class=\"d-none\" href=\"#\"></a>\n      <v-btn\n        size=\"small\"\n        class=\"ml-1\"\n        variant=\"outlined\"\n        color=\"success\"\n        @click=\"testCamundaProperties\"\n      >\n        Deploy\n      </v-btn>\n    </v-toolbar>\n    <div class=\"content flex-grow-1 elevation-2 d-flex justify-space-between\">\n      <div class=\"position-relative\">\n        <designer v-model:xml=\"demoXML\" @commandStackChanged=\"exportArtifacts\">\n          <template #right>\n        <span class=\"canvas-help d-flex flex-column\">\n          <v-btn\n            size=\"small\"\n            class=\"top-z-index\"\n            icon\n            color=\"primary\"\n            variant=\"plain\"\n            @click=\"scrollZoom(1)\"\n          >\n            <v-icon icon=\"mdi-plus\"></v-icon>\n          </v-btn>\n          <v-btn\n            size=\"small\"\n            class=\"top-z-index\"\n            icon\n            color=\"primary\"\n            variant=\"plain\"\n            @click=\"scrollZoom(-1)\"\n          >\n            <v-icon icon=\"mdi-minus\"></v-icon>\n          </v-btn>\n          <v-btn\n            size=\"small\"\n            icon\n            class=\"top-z-index\"\n            color=\"primary\"\n            variant=\"plain\"\n            @click=\"canvasFitViewport\"\n          >\n            <v-icon icon=\"mdi-arrow-u-right-top\"></v-icon>\n          </v-btn>\n          <v-btn\n            size=\"small\"\n            icon\n            class=\"top-z-index\"\n            color=\"primary\"\n            @click=\"showXml\"\n            variant=\"plain\"\n          >\n            <v-icon icon=\"mdi-xml\"></v-icon>\n          </v-btn>\n        </span>\n          </template>\n        </designer>\n      </div>\n      <aside class=\"ml-1\">\n        <properties-panel ref=\"properties\"/>\n      </aside>\n    </div>\n    <v-dialog\n      v-model=\"xmlDialog\"\n      scrollable\n      width=\"800\"\n    >\n      <v-card>\n        <v-card-title>\n          <span class=\"text-h5\">Xml Preview</span>\n          <v-spacer></v-spacer>\n          <v-btn\n            color=\"green-darken-1\"\n            icon\n            variant=\"plain\"\n            @click=\" xmlDialog= false\"\n          >\n            <v-icon icon=\"mdi-close\"></v-icon>\n          </v-btn>\n        </v-card-title>\n        <v-card-text style=\"height: 800px\">\n          <codemirror v-model=\"xmlContent\"></codemirror>\n        </v-card-text>\n      </v-card>\n    </v-dialog>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {debounce} from 'lodash-es'\nimport 'bpmn-js/dist/assets/bpmn-js.css'\nimport 'bpmn-js/dist/assets/diagram-js.css'\nimport 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'\nimport 'bpmn-js-properties-panel/dist/assets/properties-panel.css'\nimport demo from './demo3.bpmn?raw'\nimport {ref} from 'vue'\nimport PropertiesPanel from \"@/views/flowable/design/propertiesPanel/index.vue\";\nimport {Codemirror} from 'vue-codemirror'\nimport Designer from \"@/views/flowable/design/design\";\nimport {useModelStore} from '@/store'\n\nconst canvas = ref<HTMLElement | undefined>()\nconst properties = ref<InstanceType<typeof PropertiesPanel> | null>()\nconst modelStore = useModelStore()\nconst downloadSvg = ref<HTMLElement | undefined>()\nconst downloadDiagram = ref<HTMLElement | undefined>()\nconst changed = ref(false)\nconst xmlDialog = ref(false)\nconst xmlContent = ref(\"\")\nconst demoXML = ref<String>(demo)\n\nconst setEncoded = (link: HTMLElement | undefined, name: string, data: any) => {\n  var encodedData = encodeURIComponent(data);\n  if (link && data) {\n    changed.value = true\n    link.setAttribute(\n      'href', 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData\n    );\n    link.setAttribute('download', name)\n  }\n}\nconst exportArtifacts = debounce(async () => {\n  try {\n\n    const {svg} = await modelStore.getModeler.saveSVG();\n    setEncoded(downloadSvg.value, 'diagram.svg', svg);\n  } catch (err) {\n\n    console.error('Error happened saving SVG: ', err);\n    window.$snackBar?.error('Error happened saving SVG: ' + err)\n    setEncoded(downloadSvg.value, 'diagram.svg', null);\n  }\n  try {\n\n    const {xml} = await modelStore.getModeler.saveXML({format: true});\n    setEncoded(downloadDiagram.value, 'diagram.bpmn', xml);\n  } catch (err) {\n\n    console.error('Error happened saving XML: ', err);\n    window.$snackBar?.error('Error happened saving XML: ' + err)\n    setEncoded(downloadDiagram.value, 'diagram.bpmn', null);\n  }\n}, 500)\n\n\nconst testCamundaProperties = () => {\n  canvasFitViewport()\n}\n\nconst canvasFitViewport = () => {\n  modelStore.getModeler.get('canvas').zoom('fit-viewport', 'auto');\n}\n\nconst scrollZoom = (s: number) => {\n  modelStore.getModeler.get('zoomScroll').stepZoom(s);\n}\n\n\nconst showXml = async () => {\n  xmlDialog.value = true\n  const {xml} = await modelStore.getModeler.saveXML({format: true});\n  xmlContent.value = xml\n}\n</script>\n\n\n<style scoped lang=\"scss\">\n\n.content {\n\n  .canvas-help {\n    position: absolute;\n    right: 10px;\n  }\n\n  .canvas {\n    background-color: rgb(var(--v-theme-surface));\n    position: absolute;\n  }\n\n  &, div {\n    width: 100%;\n    height: 100%;\n\n    :deep(.bjs-powered-by) {\n      display: none;\n    }\n\n  }\n}\n\n.properties-panel-parent {\n  border-left: 1px solid rgb(var(--v-theme-background));\n  overflow: auto;\n\n  &:empty {\n    display: none;\n  }\n\n  > .djs-properties-panel {\n    padding-bottom: 70px;\n    min-height: 100%;\n  }\n}\n\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/initModeler.ts",
    "content": "import {markRaw} from 'vue'\nimport {useModelStore} from '@/store'\nimport BpmnModeler from \"bpmn-js/lib/Modeler\";\nimport EventEmitter from \"@/utils/flow/EventEmitter\";\n\nexport default function (\n  options: any,\n  emit: (event: any, ...args: any[]) => void\n) {\n\n  const store = useModelStore()\n\n  store.getModeler && store.getModeler.destroy()\n  store.setModeler(null)\n  const modeler = new BpmnModeler(options)\n  store.setModeler(markRaw(modeler))\n  store.setModules('moddle', markRaw(modeler.get('moddle')))\n  store.setModules('modeling', markRaw(modeler.get('modeling')))\n  store.setModules('canvas', markRaw(modeler.get('canvas')))\n  store.setModules('commandStack', markRaw(modeler.get('commandStack')))\n  store.setModules('elementRegistry', markRaw(modeler.get('elementRegistry')))\n\n  EventEmitter.emit('modeler-init', modeler)\n\n  modeler.on('commandStack.changed', async (event: any) => {\n    try {\n      const {xml} = await modeler.saveXML({format: true})\n      emit('update:xml', xml)\n      emit('command-stack-changed', event)\n    } catch (error) {\n      console.error(error)\n    }\n  })\n}\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/actions.vue",
    "content": "<template>\n  <v-expansion-panel\n    title=\"Actions\"\n  >\n    <v-expansion-panel-text>\n      <v-select variant=\"outlined\" label=\"Action\" density=\"comfortable\" hide-details\n                multiple\n                :items=\"actionOption\"\n                v-model=\"actions\" @update:modelValue=\"updateElementActions\"></v-select>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport {getBusinessObject} from \"bpmn-js/lib/util/ModelUtil\";\n\nconst actionOption = ref(['approve', 'disapprove'])\nconst actions = ref<Array<String>>([])\nconst modelStore = useModelStore()\nconst element = modelStore.getActive\nconst businessObject = getBusinessObject(element);\n\nconst camundaPropertyName = \"camunda:userActions\"\n\nconst getActions: () => Array<String> = () => {\n  return businessObject.get(camundaPropertyName);\n};\n\nif (getActions()) {\n  actions.value = getActions()\n}\n\nconst updateElementActions = (value: Array<String>) => {\n  const properties: Record<string, any> = {}\n  properties[camundaPropertyName] = value\n  modelStore.getCommandStack.execute('element.updateModdleProperties', {\n    element,\n    moddleElement: getBusinessObject(element),\n    properties,\n  });\n}\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/condition.vue",
    "content": "<template>\n  <v-expansion-panel\n    title=\"Condition\"\n  >\n    <v-expansion-panel-text>\n      <v-select variant=\"outlined\" label=\"opinion\" density=\"comfortable\" hide-details\n                color=\"primary\"\n                multiple\n                chips\n                v-model=\"opinionRef\"\n                :items=\"opinionOption\"\n                @update:modelValue=\"updateElementFormRef\"></v-select>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport {getBusinessObject} from \"bpmn-js/lib/util/ModelUtil\";\n\nconst opinionOption = ref(['approve', 'disapprove'])\nconst opinionRef = ref<Array<String>>([])\nconst modelStore = useModelStore()\nconst element = modelStore.getActive\nconst businessObject = getBusinessObject(element);\n\nconst getFormRef: () => Array<String> = () => {\n  return businessObject.get('camunda:opinionRef');\n};\n\nif (getFormRef()) {\n  opinionRef.value = getFormRef()\n}\n\nconst updateElementFormRef = (value: String) => {\n  modelStore.getCommandStack.execute('element.updateModdleProperties', {\n    element,\n    moddleElement: getBusinessObject(element),\n    properties: {\n      'camunda:opinionRef': value\n    }\n  });\n}\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/documentation.vue",
    "content": "<template>\n  <v-expansion-panel\n    title=\"Documentation\"\n  >\n    <v-expansion-panel-text>\n      <v-textarea variant=\"outlined\" label=\"Element Documentation\" density=\"comfortable\" hide-details\n                  :model-value=\"elementDoc\" @change=\"updateElementDoc\"></v-textarea>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport EventEmitter from \"@/utils/flow/EventEmitter\";\nimport {getDocumentValue, setDocumentValue} from \"@/views/flowable/bo-utils/documentationUtil\";\n\nconst elementDoc = ref<String>(\"\")\n\nconst modelStore = useModelStore()\n\nconst updateElementDoc = (value: any) => {\n  setDocumentValue(modelStore.getActive, value.target.value)\n}\n\nonMounted(() => {\n  elementDoc.value = getDocumentValue(modelStore.getActive || '')\n  EventEmitter.on('element-update', () => {\n    elementDoc.value = getDocumentValue(modelStore.getActive || '')\n  })\n})\n\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/form.vue",
    "content": "<template>\n  <v-expansion-panel\n    title=\"Forms\"\n  >\n    <v-expansion-panel-text>\n      <v-select variant=\"outlined\" label=\"form\" density=\"comfortable\" hide-details\n                v-model=\"formRef\"\n                :items=\"formOption\"\n                @update:modelValue=\"updateElementFormRef\"></v-select>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport {getBusinessObject} from \"bpmn-js/lib/util/ModelUtil\";\n\nconst formOption = ref(['ticket_form', 'review_form'])\nconst formRef = ref<String>(\"\")\nconst modelStore = useModelStore()\nconst element = modelStore.getActive\nconst businessObject = getBusinessObject(element);\n\nconst getFormRef: () => String = () => {\n  return businessObject.get('camunda:formRef');\n};\n\nif (getFormRef()) {\n  formRef.value = getFormRef()\n}\n\nconst updateElementFormRef = (value: String) => {\n  modelStore.getCommandStack.execute('element.updateModdleProperties', {\n    element,\n    moddleElement: getBusinessObject(element),\n    properties: {\n      'camunda:formRef': value\n    }\n  });\n}\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/general.vue",
    "content": "<template>\n\n  <v-expansion-panel\n    title=\"General\"\n  >\n    <v-expansion-panel-text>\n      <v-text-field variant=\"outlined\" label=\"name\" density=\"comfortable\" hide-details\n                    :model-value=\"elementName\" @change=\"updateElementName\"></v-text-field>\n      <v-text-field variant=\"outlined\" label=\"id\" density=\"comfortable\" hide-details\n                    :model-value=\"elementId\" @change=\"updateElementId\"></v-text-field>\n      <template v-if=\"isProcess\">\n        <v-text-field variant=\"outlined\" label=\"versionTag\" density=\"comfortable\" hide-details\n                      :model-value=\"elementVersion\" @change=\"updateElementVersion\"></v-text-field>\n        <v-checkbox label=\"executable\" hide-details density=\"comfortable\"\n                    :model-value=\"elementExecutable\" @change=\"updateElementExecutable\"></v-checkbox>\n      </template>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport {getNameValue, setNameValue} from \"@/views/flowable/bo-utils/nameUtil\";\nimport {\n  getProcessExecutable,\n  getProcessVersionTag,\n  setProcessExecutable,\n  setProcessVersionTag\n} from \"@/views/flowable/bo-utils/processUtil\";\nimport EventEmitter from \"@/utils/flow/EventEmitter\";\nimport {setIdValue} from \"@/views/flowable/bo-utils/idUtil\";\n\nconst isProcess = ref<Boolean>(false)\nconst elementName = ref<String>(\"\")\nconst elementId = ref<String>(\"\")\nconst elementExecutable = ref<Boolean>(true)\nconst elementVersion = ref<String>(\"\")\n\nconst modelStore = useModelStore()\n\nconst reloadGenerationData = () => {\n  isProcess.value = !!modelStore.getActive && modelStore.getActive.type === 'bpmn:Process'\n  elementId.value = modelStore.getActiveId as string\n  elementName.value = getNameValue(modelStore.getActive) || ''\n  if (isProcess.value) {\n    elementExecutable.value = getProcessExecutable(modelStore.getActive)\n    elementVersion.value = getProcessVersionTag(modelStore.getActive) || ''\n  }\n}\n\nconst updateElementName = (value: any) => {\n  setNameValue(modelStore.getActive, value.target.value)\n}\nconst updateElementId = (value: any) => {\n  setIdValue(modelStore.getActive, value.target.value)\n}\nconst updateElementVersion = (value: any) => {\n  const reg = /((\\d|([1-9](\\d*))).){2}(\\d|([1-9](\\d*)))/\n  if (reg.test(value.target.value)) {\n    setProcessVersionTag(modelStore.getActive, value.target.value)\n  } else {\n    window.$snackBar?.error('版本号必须符合语义化版本2.0.0 要点')\n  }\n}\nconst updateElementExecutable = (value: boolean) => {\n  setProcessExecutable(modelStore.getActive, value)\n}\n\nonMounted(() => {\n  reloadGenerationData()\n  EventEmitter.on('element-update', reloadGenerationData)\n})\n\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/components/userAssigne.vue",
    "content": "<template>\n  <v-expansion-panel\n    title=\"User Assignment\"\n  >\n    <v-expansion-panel-text>\n      <v-select label=\"Type\" v-model=\"type\" :items=\"typeOption\" density=\"comfortable\" hide-details\n                variant=\"outlined\"></v-select>\n      <v-select variant=\"outlined\" label=\"User\" density=\"comfortable\" hide-details\n                :items=\"['frank','silva','lulu','xiaowang','xiaosun']\"\n                v-if=\"type==='user'\"\n                @update:modelValue=\"updateElementUser\"\n                v-model=\"userIds\"\n                chips\n                multiple\n      ></v-select>\n      <v-select variant=\"outlined\" label=\"Role\" density=\"comfortable\" hide-details\n                :items=\"['developer','tester','manager']\"\n                v-model=\"roleIds\"\n                chips\n                v-if=\"type==='role'\"\n                multiple\n      ></v-select>\n\n      <div class=\"d-flex flex-row\">\n        <v-text-field\n          v-model=\"dueDate\"\n          label=\"DueDate\"\n          density=\"comfortable\"\n          hide-details\n          class=\"ma-0\"\n          variant=\"outlined\"\n          @change=\"updateElementDueDateTime\"\n          type=\"date\"\n        ></v-text-field>\n        <v-text-field\n          v-model=\"dueTime\"\n          density=\"comfortable\"\n          hide-details\n          variant=\"outlined\"\n          @change=\"updateElementDueDateTime\"\n          type=\"time\"\n        ></v-text-field>\n      </div>\n    </v-expansion-panel-text>\n  </v-expansion-panel>\n\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useModelStore} from '@/store'\nimport {getBusinessObject} from \"bpmn-js/lib/util/ModelUtil\";\n\nconst camundaDueDate = \"camunda:dueDate\"\nconst typeOption = ['user', 'role']\nconst type = ref(\"\")\nconst dueDate = ref<String>(\"\")\nconst dueTime = ref<String>(\"\")\nconst userIds = ref<Array<String>>([])\nconst roleIds = ref<Array<String>>([])\n\nconst modelStore = useModelStore()\nconst element = modelStore.getActive\n\nconst businessObject = getBusinessObject(element);\n\n\nconst getUserIdsValue: () => Array<String> = () => {\n  return businessObject.get('camunda:assignee');\n};\n\nif (getUserIdsValue()) {\n  userIds.value = getUserIdsValue()\n  type.value = typeOption[0]\n}\n\nconst getDueDate: () => Array<String> = () => {\n  return businessObject.get(camundaDueDate)?.split(' ');\n};\n\nif (getDueDate()) {\n  const arr = getDueDate()\n  if (arr.length > 0)\n    dueDate.value = arr[0]\n  if (arr.length > 1)\n    dueTime.value = arr[1]\n}\n\nconst updateElementUser = (value: Array<String>) => {\n  modelStore.getCommandStack.execute('element.updateModdleProperties', {\n    element,\n    moddleElement: getBusinessObject(element),\n    properties: {\n      'camunda:assignee': value\n    }\n  });\n}\nconst updateElementDueDateTime = (value: any) => {\n  if (dueDate.value.length <= 0) {\n    return\n  }\n  modelStore.getCommandStack.execute('element.updateModdleProperties', {\n    element,\n    moddleElement: getBusinessObject(element),\n    properties: {\n      'camunda:dueDate': dueDate.value + \" \" + dueTime.value\n    }\n  });\n}\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/propertiesPanel/index.vue",
    "content": "<template>\n  <v-card\n    class=\"properties-panel position-relative\"\n    width=\"280\"\n  >\n    <div class=\"position-absolute\"\n         style=\"left: 0;right: 0;top: 0;bottom: 0\"\n    >\n      <v-card-title>{{ panelTitle }}</v-card-title>\n      <v-expansion-panels\n        variant=\"accordion\"\n        multiple>\n\n        <component v-for=\"(c) in renderComponents\" :is=\"c\"></component>\n\n      </v-expansion-panels>\n    </div>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\n\n\nimport {markRaw, ref} from \"vue\";\nimport type {Component} from \"vue\";\nimport General from \"./components/general.vue\";\nimport Documentation from \"./components/documentation.vue\";\nimport UserService from \"./components/userAssigne.vue\";\nimport Form from \"./components/form.vue\";\nimport Opinion from \"./components/actions.vue\";\nimport Condition from \"./components/condition.vue\";\nimport {debounce} from 'lodash-es'\nimport EventEmitter from \"@/utils/flow/EventEmitter\";\nimport {useModelStore} from '@/store'\nimport {isUserService, isStartEvent} from \"@/views/flowable/bo-utils/userTaskUtil\";\nimport {isNotConditional} from \"@/views/flowable/bo-utils/conditionalUtil\";\n\nconst modelStore = useModelStore()\nconst renderComponents = ref<Component[]>([])\nconst setCurrentComponents = (activatedElement: any) => {\n\n  renderComponents.value.splice(0, renderComponents.value.length)\n  nextTick(() => {\n    renderComponents.value.push(markRaw(General))\n    renderComponents.value.push(markRaw(Documentation))\n\n    isUserService(activatedElement) &&\n    renderComponents.value.push(markRaw(UserService)) &&\n    renderComponents.value.push(markRaw(Form)) &&\n    renderComponents.value.push(markRaw(Opinion))\n\n    isStartEvent(activatedElement) &&\n    renderComponents.value.push(markRaw(Form))\n\n\n    !isNotConditional(activatedElement) &&\n    renderComponents.value.push(markRaw(Condition))\n  })\n}\n\nlet Modeler: any = null\nconst currentElementId = ref<String | undefined>()\nconst currentElementType = ref<String | undefined>()\nconst panelTitle = ref<String | undefined>()\nconst setCurrentElement = debounce((activatedElement?: any) => {\n  if (!activatedElement) {\n    activatedElement =\n      Modeler?.get('elementRegistry').find((el: any) => el.type === 'bpmn:Process') ||\n      Modeler?.get('elementRegistry').find((el: any) => el.type === 'bpmn:Collaboration')\n    if (!activatedElement) {\n      return console.error('No Element found!')\n    }\n  }\n\n  modelStore.setElement(markRaw(activatedElement), activatedElement.id)\n\n  currentElementId.value = activatedElement.id\n  currentElementType.value = activatedElement.type.split(\":\")[1]\n  panelTitle.value = Modeler?.get('translate')(currentElementType.value)\n  setCurrentComponents(activatedElement)\n  EventEmitter.emit('element-update', activatedElement)\n\n}, 100)\n\nonMounted(() => !currentElementId.value && setCurrentElement())\n\nconst initialize = () => {\n  EventEmitter.on(\"modeler-init\", (modeler: any) => {\n    Modeler = modeler\n    const eventBus = modeler.get('eventBus')\n    // 导入完成后默认选中 process 节点\n    eventBus.on('import.done', () => {\n      setCurrentElement(null)\n    })\n    // 监听选择事件，修改当前激活的元素以及表单\n    eventBus.on('selection.changed', ({newSelection}: { newSelection: any }) => {\n      setCurrentElement(newSelection[0] || null)\n    })\n    eventBus.on('element.changed', ({element}: { element: any }) => {\n      // if (element && element.id === currentElementId.value) {\n      //   setCurrentElement(element)\n      // }\n    })\n  })\n}\ninitialize()\n\n</script>\n\n<style scoped lang=\"scss\">\n.properties-panel {\n  overflow-y: scroll;\n\n  .v-expansion-panel {\n    border-radius: 0;\n  }\n\n  &:deep(.v-expansion-panel-text__wrapper) {\n    padding: 8px 8px;\n\n    .v-input:not(:last-child) {\n      margin-bottom: 8px;\n    }\n  }\n\n  &:deep(.v-expansion-panel-title) {\n    min-height: 24px !important;\n  }\n}\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/design/provider/index.js",
    "content": "import SelfProvider from './selfProvider';\n\nexport default {\n  __init__: [ 'magicPropertiesProvider' ],\n  magicPropertiesProvider: [ 'type', SelfProvider ]\n};\n"
  },
  {
    "path": "src/views/flowable/design/provider/parts/FormProps.js",
    "content": "import {SelectEntry, isSelectEntryEdited} from '@bpmn-io/properties-panel';\nimport {useService} from 'bpmn-js-properties-panel'\nimport {getBusinessObject} from 'bpmn-js/lib/util/ModelUtil';\n\nexport function FormProps(props) {\n  return [\n    {\n      id: \"form\",\n      component: Form,\n      isEdited: isSelectEntryEdited,\n    }\n  ]\n}\n\nfunction Form(props) {\n  const {element} = props;\n  const translate = useService('translate');\n  const bpmnFactory = useService('bpmnFactory');\n  const businessObject = getBusinessObject(element);\n  const commandStack = useService('commandStack');\n\n  let extensionElements = businessObject.get('extensionElements');\n\n  const getOptions = () => {\n    return [\n      {value: '', label: translate('<none>')},\n      {value: 'formRef', label: translate('Camunda Forms')},\n      {value: 'formKey', label: translate('Embedded or External Task Forms')},\n      {value: 'formData', label: translate('Generated Task Forms')},\n      {value: 'fuck', label: translate('Generated Task Forms')}\n    ];\n  }\n\n  const setValue = (value) => {\n    console.log(value)\n  }\n  const getValue = () => {\n    return 'fuck'\n  }\n\n  return SelectEntry({\n    element,\n    id: 'form',\n    label: translate('FormRef'),\n    getOptions,\n    setValue,\n    getValue\n  })\n}\n"
  },
  {
    "path": "src/views/flowable/design/provider/parts/SpellProps.js",
    "content": "import {SelectEntry, isSelectEntryEdited} from '@bpmn-io/properties-panel';\nimport {useService} from 'bpmn-js-properties-panel'\n\n// import hooks from the vendored preact package\nimport {useEffect, useState} from '@bpmn-io/properties-panel/preact/hooks';\n\nexport default function (element) {\n\n  return [\n    {\n      id: 'lulu-formType',\n      element,\n      component: FormType,\n      isEdited: isSelectEntryEdited\n    }\n  ];\n}\n\nfunction FormType(props) {\n  const {element, id} = props;\n\n  const modeling = useService('modeling');\n  const translate = useService('translate');\n  const debounce = useService('debounceInput');\n\n\n  const getValue = () => {\n    return element.businessObject.spell || '';\n  }\n\n  const setValue = value => {\n    return modeling.updateProperties(element, {\n      spell: value\n    });\n  }\n\n  const [spells, setSpells] = useState([]);\n\n  useEffect(() => {\n    function fetchSpells() {\n      fetch('http://localhost:1234/spell')\n        .then(res => res.json())\n        .then(spellbook => setSpells(spellbook))\n        .catch(error => console.error(error));\n    }\n\n    fetchSpells();\n  }, [setSpells]);\n\n  const getOptions = () => {\n    return [\n      {label: '<none>', value: undefined},\n      ...spells.map(spell => ({\n        label: spell,\n        value: spell\n      }))\n    ];\n  }\n\n  return SelectEntry(\n    {\n      id,\n      element,\n      description: translate('Apply a black magic spell'),\n      label: translate('FormType'),\n      getValue,\n      setValue,\n      getOptions,\n      debounce\n    }\n  )\n}\n"
  },
  {
    "path": "src/views/flowable/design/provider/selfProvider.js",
    "content": "import spellProps from './parts/SpellProps';\n\nimport {is} from 'bpmn-js/lib/util/ModelUtil';\nimport {FormProps} from \"@/views/flowable/design/provider/parts/FormProps\";\n\nconst LOW_PRIORITY = 500;\n\n\nexport default function SelfProvider(propertiesPanel, translate) {\n\n  this.getGroups = function (element) {\n\n    return function (groups) {\n      deleteById(groups, \"CamundaPlatform__FormData\")\n      const form = findGroup(groups, 'CamundaPlatform__Form')\n\n      if (form) {\n        form.entries = [\n          ...FormProps({element})\n        ]\n      }\n      return groups;\n    }\n  };\n  propertiesPanel.registerProvider(LOW_PRIORITY, this);\n}\n\nSelfProvider.$inject = ['translate'];\n\n// Create the custom magic group\nfunction createMagicGroup(element, translate) {\n\n  // create a group called \"Magic components\".\n  const magicGroup = {\n    id: 'magic',\n    label: translate('Magic components'),\n    entries: spellProps(element)\n  };\n\n  return magicGroup\n}\n\nfunction findGroup(groups, id) {\n  return groups.find(g => g.id === id);\n}\n\nfunction findEntry(entries, id) {\n  return entries.find(g => g.id === id);\n}\n\nfunction deleteById(array, id) {\n  const index = array.findIndex(g => g.id === id)\n  if (index > -1) {\n    array.splice(index, 1)\n  }\n}\n"
  },
  {
    "path": "src/views/flowable/design/translations.ts",
    "content": "const translations: Record<string, string> = {\n  \"Activate the create/remove space tool\": \"启动创建/删除空间工具\",\n  \"Activate the global connect tool\": \"启动全局连接工具\",\n  \"Activate the hand tool\": \"启动手动工具\",\n  \"Activate the lasso tool\": \"启动 Lasso 工具\",\n  \"Ad-hoc\": \"Ad-hoc子流程\",\n  \"Add Lane above\": \"添加到通道之上\",\n  \"Add Lane below\": \"添加到通道之下\",\n  \"Append ConditionIntermediateCatchEvent\": \"添加中间条件捕获事件\",\n  \"Append element\": \"添加元素\",\n  \"Append EndEvent\": \"添加结束事件\",\n  \"Append Gateway\": \"添加网关\",\n  \"Append Intermediate/Boundary Event\": \"添加中间/边界事件\",\n  \"Append MessageIntermediateCatchEvent\": \"添加消息中间捕获事件\",\n  \"Append ReceiveTask\": \"添加接收任务\",\n  \"Append SignalIntermediateCatchEvent\": \"添加信号中间捕获事件\",\n  \"Append Task\": \"添加任务\",\n  \"Append TimerIntermediateCatchEvent\": \"添加定时器中间捕获事件\",\n  \"Append compensation activity\": \"追加补偿活动\",\n  \"Append {type}\": \"追加 {type}\",\n  \"Boundary Event\": \"边界事件\",\n  \"Business Rule Task\": \"规则任务\",\n  \"Call Activity\": \"引用流程\",\n  \"Cancel Boundary Event\": \"取消边界事件\",\n  \"Cancel End Event\": \"取消结束事件\",\n  \"Change type\": \"更改类型\",\n  \"Collapsed Pool\": \"折叠池\",\n  \"Collection\": \"集合\",\n  \"Compensation Boundary Event\": \"补偿边界事件\",\n  \"Compensation End Event\": \"结束补偿事件\",\n  \"Compensation Intermediate Throw Event\": \"中间补偿抛出事件\",\n  \"Compensation Start Event\": \"补偿启动事件\",\n  \"Complex Gateway\": \"复杂网关\",\n  \"Conditional Boundary Event\": \"条件边界事件\",\n  \"Conditional Boundary Event (non-interrupting)\": \"条件边界事件 (非中断)\",\n  \"Conditional Flow\": \"条件流\",\n  \"Conditional Intermediate Catch Event\": \"中间条件捕获事件\",\n  \"Conditional Start Event\": \"条件启动事件\",\n  \"Conditional Start Event (non-interrupting)\": \"条件启动事件 (非中断)\",\n  \"Connect using Association\": \"文本关联\",\n  \"Connect using DataInputAssociation\": \"数据关联\",\n  \"Connect using Sequence/MessageFlow or Association\": \"消息关联\",\n  \"Create IntermediateThrowEvent/BoundaryEvent\": \"创建中间抛出/边界事件\",\n  \"Create DataObjectReference\": \"创建数据对象引用\",\n  \"Create DataStoreReference\": \"创建数据存储引用\",\n  \"Create element\": \"创建元素\",\n  \"Create EndEvent\": \"创建结束事件\",\n  \"Create Gateway\": \"创建网关\",\n  \"Create Group\": \"创建组\",\n  \"Create Intermediate/Boundary Event\": \"创建中间/边界事件\",\n  \"Create Pool/Participant\": \"创建池/参与者\",\n  \"Create StartEvent\": \"创建开始事件\",\n  \"Create Task\": \"创建任务\",\n  \"Create expanded SubProcess\": \"创建可折叠子流程\",\n  \"Create {type}\": \"创建 {type}\",\n  \"Data\": \"数据\",\n  \"Data Object Reference\": \"数据对象引用\",\n  \"Data Store Reference\": \"数据存储引用\",\n  \"Default Flow\": \"默认流\",\n  \"Divide into three Lanes\": \"分成三条通道\",\n  \"Divide into two Lanes\": \"分成两条通道\",\n  \"Empty Pool\": \"空泳道\",\n  \"Empty Pool (removes content)\": \"清空泳道（删除内容）\",\n  \"End Event\": \"结束事件\",\n  \"Error Boundary Event\": \"错误边界事件\",\n  \"Error End Event\": \"结束错误事件\",\n  \"Error Start Event\": \"错误启动事件\",\n  \"Escalation Boundary Event\": \"升级边界事件\",\n  \"Escalation Boundary Event (non-interrupting)\": \"升级边界事件 (非中断)\",\n  \"Escalation End Event\": \"结束升级事件\",\n  \"Escalation Intermediate Throw Event\": \"中间升级抛出事件\",\n  \"Escalation Start Event\": \"升级启动事件\",\n  \"Escalation Start Event (non-interrupting)\": \"升级启动事件 (非中断)\",\n  \"Events\": \"事件\",\n  \"Event Sub Process\": \"事件子流程\",\n  \"Event based Gateway\": \"事件网关\",\n  \"Exclusive Gateway\": \"独占网关\",\n  \"Expanded Pool\": \"展开泳道\",\n  \"Gateways\": \"网关\",\n  \"Inclusive Gateway\": \"包容网关\",\n  \"Intermediate Throw Event\": \"中间抛出事件\",\n  \"Link Intermediate Catch Event\": \"中间链接捕获事件\",\n  \"Link Intermediate Throw Event\": \"中间链接抛出事件\",\n  \"Loop\": \"循环\",\n  \"Manual Task\": \"手动任务\",\n  \"Message Boundary Event\": \"消息边界事件\",\n  \"Message Boundary Event (non-interrupting)\": \"消息边界事件 (非中断)\",\n  \"Message End Event\": \"结束消息事件\",\n  \"Message Intermediate Catch Event\": \"中间消息捕获事件\",\n  \"Message Intermediate Throw Event\": \"中间消息抛出事件\",\n  \"Message Start Event\": \"消息启动事件\",\n  \"Message Start Event (non-interrupting)\": \"消息启动事件 (非中断)\",\n  \"Parallel Gateway\": \"并行网关\",\n  \"Parallel Multi Instance\": \"并行多实例\",\n  \"Participants\": \"参与者\",\n  \"Participant Multiplicity\": \"参与者多重性\",\n  \"Receive Task\": \"接受任务\",\n  \"Remove\": \"移除\",\n  \"Script Task\": \"脚本任务\",\n  \"Send Task\": \"发送任务\",\n  \"Sequence Flow\": \"顺序流\",\n  \"Sequential Multi Instance\": \"串行多实例\",\n  \"Service Task\": \"服务任务\",\n  \"Signal Boundary Event\": \"信号边界事件\",\n  \"Signal Boundary Event (non-interrupting)\": \"信号边界事件 (非中断)\",\n  \"Signal End Event\": \"结束信号事件\",\n  \"Signal Intermediate Catch Event\": \"中间信号捕获事件\",\n  \"Signal Intermediate Throw Event\": \"中间信号抛出事件\",\n  \"Signal Start Event\": \"信号启动事件\",\n  \"Signal Start Event (non-interrupting)\": \"信号启动事件 (非中断)\",\n  \"Start Event\": \"开始事件\",\n  \"Sub Process\": \"子流程\",\n  \"Sub Processes\": \"子流程\",\n  \"Sub Process (collapsed)\": \"可折叠子流程\",\n  \"Sub Process (expanded)\": \"可展开子流程\",\n  \"Task\": \"任务\",\n  \"Tasks\": \"任务\",\n  \"Terminate End Event\": \"终止边界事件\",\n  \"Timer Boundary Event\": \"定时边界事件\",\n  \"Timer Boundary Event (non-interrupting)\": \"定时边界事件 (非中断)\",\n  \"Timer Intermediate Catch Event\": \"中间定时捕获事件\",\n  \"Timer Start Event\": \"定时启动事件\",\n  \"Timer Start Event (non-interrupting)\": \"定时启动事件 (非中断)\",\n  \"Transaction\": \"事务\",\n  \"User Task\": \"用户任务\",\n  \"already rendered {element}\": \"{element} 已呈现\",\n  \"diagram not part of bpmn:Definitions\": \"图表不是 bpmn:Definitions 的一部分\",\n  \"element required\": \"需要元素\",\n  \"correcting missing bpmnElement on {plane} to {rootElement}\": \"在 {plane} 上更正缺失的 bpmnElement 为 {rootElement}\",\n  \"element {element} referenced by {referenced}#{property} not yet drawn\":\n    \"元素 {element} 的引用 {referenced}#{property} 尚未绘制\",\n  \"failed to import {element}\": \"{element} 导入失败\",\n  \"flow elements must be children of pools/participants\": \"元素必须是池/参与者的子级\",\n  \"more than {count} child lanes\": \"超过 {count} 条通道\",\n  \"missing {semantic}#attachedToRef\": \"在 {element} 中缺少 {semantic}#attachedToRef\",\n  \"multiple DI elements defined for {element}\": \"为 {element} 定义了多个 DI 元素\",\n  \"no bpmnElement referenced in {element}\": \"{element} 中没有引用 bpmnElement\",\n  \"no diagram to display\": \"没有要显示的图表\",\n  \"no shape type specified\": \"未指定形状类型\",\n  \"no parent for {element} in {parent}\": \"在 {element} 中没有父元素 {parent}\",\n  \"no process or collaboration to display\": \"没有可显示的流程或协作\",\n  \"out of bounds release\": \"越界释放\",\n  \"General\": \"通用\"\n};\n\nexport default translations\n"
  },
  {
    "path": "src/views/flowable/index.vue",
    "content": "<template>\n\n</template>\n\n<script setup lang=\"ts\">\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/flowable/utils/BpmnValidator.ts",
    "content": "const SPACE_REGEX = /\\s/\n\n// for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar\n// | \"-\" | \".\" | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]\nconst QNAME_REGEX = /^([a-z][\\w-.]*:)?[a-z_][\\w-.]*$/i\n\n// for ID validation as per BPMN Schema (QName - Namespace)\nconst ID_REGEX = /^[a-z_][\\w-.]*$/i\n\nexport function containsSpace(value: string) {\n  return SPACE_REGEX.test(value)\n}\n\n/**\n * checks whether the id value is valid\n *\n * @param {ModdleElement} element\n * @param {String} idValue\n * @param {Function} translate\n *\n * @return {String} error message\n */\nexport function isIdValid(element: any, idValue: any) {\n  const assigned = element.$model.ids.assigned(idValue)\n  const idAlreadyExists = assigned && assigned !== element\n\n  if (!idValue) {\n    return 'ID required.'\n  }\n\n  if (idAlreadyExists) {\n    return 'ID must unique'\n  }\n\n  return validateId(idValue)\n}\n\nexport function validateId(idValue: any) {\n  if (containsSpace(idValue)) {\n    return 'ID can not contains space'\n  }\n\n  if (!ID_REGEX.test(idValue)) {\n    if (QNAME_REGEX.test(idValue)) {\n      return 'ID 不能包含前缀'\n    }\n\n    return 'ID 必须符合 BPMN 规范'\n  }\n}\n"
  },
  {
    "path": "src/views/form/design/components/formitem.tsx",
    "content": "import {defineComponent, toRefs} from 'vue'\nimport {\n  VTextField,\n  VChip, VBtn, VCheckbox, VTextarea, VSwitch, VSelect, VFileInput,\n  VRadioGroup,\n  VRadio,\n  VCol\n} from 'vuetify/components'\n\nimport {PropType} from \"vue\";\nimport VueDraggable from 'vuedraggable'\nimport Formitem from \"@/views/form/design/components/formitem\";\nimport {VForm} from \"vuetify/components/VForm\";\nimport {cloneDeep, uniqueId} from \"lodash-es\";\nimport {VRow} from \"vuetify/components/VGrid\";\n\nexport default defineComponent({\n  props: {\n    item: {\n      required: true,\n      type: Object as PropType<formComponent>,\n      default: () => {\n      }\n    },\n    active: {\n      required: false,\n      type: Object as PropType<formComponent>,\n      default: () => {\n      }\n    },\n    layers: {\n      required: false,\n      type: Number,\n      default: 0\n    },\n    list: {\n      required: false,\n      type: Array as PropType<Array<formComponent>>,\n      default: () => []\n    },\n    idPrefix: {\n      required: false,\n      type: String,\n      default: 'formComponent'\n    },\n    preview: {\n      required: false,\n      type: Boolean,\n      default: false,\n    }\n  },\n  emits: {\n    'delete': (e: formComponent) => {\n      return e\n    },\n    'selected': (e: formComponent) => {\n      return e\n    },\n    'duplicate': (e: formComponent) => {\n      return e\n    },\n  },\n  setup(props, {emit}) {\n    const {active, item} = toRefs(props)\n    const zIndexStyle = 'z-index: ' + props.layers * 10\n\n    const cloneComponent = (c: formComponent) => {\n      const clone = cloneDeep(c)\n      clone.id = props.idPrefix + '_' + uniqueId()\n      recursiveIdGenerator(clone.config.formChildren)\n      return clone\n    }\n\n    const recursiveIdGenerator = (list: formComponent[]) => {\n      if (!list)\n        return\n      list.forEach(c => {\n        c.id = props.idPrefix + '_' + uniqueId()\n        if (c.config.formChildren) {\n          recursiveIdGenerator(c.config.formChildren)\n        }\n      })\n    }\n\n    const deleteComponent = (list: formComponent[], c: formComponent) => {\n      const index = list.findIndex(i => i.id == c.id)\n      if (index > -1) {\n        list.splice(index, 1)\n      }\n    }\n    const duplicateComponent = (list: formComponent[], c: formComponent) => {\n      const find = list.find(i => i.id == c.id)\n      if (find) {\n        const tmp = cloneComponent(find)\n        list.splice(list.indexOf(find), 0, tmp)\n      }\n    }\n\n    const emitSelected = (c: formComponent) => {\n      emit('selected', c)\n    }\n    const emitDelete = (c: formComponent) => {\n      emit('delete', c)\n      deleteComponent(props.list, c)\n    }\n    const emitDuplicate = (c: formComponent) => {\n      emit('duplicate', c)\n      duplicateComponent(props.list, c)\n    }\n\n\n    const flexRawRender = (item: formComponent) => {\n      // @ts-ignore\n\n      if (props.preview) {\n        return <div class={['d-flex', 'flex-row', 'align-center']}>\n          {item.config.formChildren.map((c: formComponent) => {\n            return <Formitem\n              preview={true}\n              item={c}></Formitem>\n          })}\n        </div>\n      }\n\n      return <VForm style={zIndexStyle}>\n\n        <VueDraggable itemKey={'id'}\n                      animation={340}\n                      group={\"formComponentGroups\"}\n                      ghostClass={\"ghost\"}\n                      class={[\"d-flex\", 'flex-row', 'align-center']}\n                      style={'min-height:60px'}\n                      list={item.config.formChildren}>\n          {{\n            default: () => <VRow></VRow>,\n            item: ({element}: { element: formComponent }) =>\n              <Formitem active={props.active}\n                        list={item.config.formChildren}\n                        onDelete={(e: formComponent) => emitDelete(e)}\n                        onDuplicate={(e: formComponent) => emitDuplicate(e)}\n                        onSelected={(e: formComponent) => emitSelected(e)}\n                        layers={props.layers + 1}\n                        item={element}></Formitem>\n\n          }}\n        </VueDraggable>\n      </VForm>\n    }\n\n    const switchRender = (item: formComponent) => {\n      switch (item.type) {\n        case \"button\":\n          return <VBtn color={item.config.color} density={item.config.density} class={'text-white'}>{item.name}</VBtn>\n        case \"checkbox\":\n          return <>{item.config.options.map((o: formOption) => {\n            return <VCheckbox label={o.label} density={item.config.density} hideDetails={true}\n                              class={['d-inline-block']}></VCheckbox>\n          })}</>\n        case \"switch\":\n          return <VSwitch label={item.name} density={item.config.density}></VSwitch>\n        case \"textField\":\n          return <VTextField label={item.name} hideDetails={true} density={item.config.density}\n                             variant={item.config.variant}></VTextField>\n        case \"textArea\":\n          return <VTextarea label={item.name} hideDetails={true} density={item.config.density}\n                            variant={item.config.variant}></VTextarea>\n        case \"date\":\n          return <VTextField label={item.name} hideDetails={true} density={item.config.density} type={'date'}\n                             variant={item.config.variant}></VTextField>\n        case \"time\":\n          return <VTextField label={item.name} hideDetails={true} density={item.config.density} type={'time'}\n                             variant={item.config.variant}></VTextField>\n        case \"number\":\n          return <VTextField label={item.name} hideDetails={true} density={item.config.density} type={'number'}\n                             variant={item.config.variant}></VTextField>\n        case \"select\":\n          return <VSelect label={item.name} hideDetails={true} items={item.config.options}\n                          itemTitle={'label'}\n                          itemValue={'value'} density={item.config.density}\n                          variant={item.config.variant}></VSelect>\n        case \"user\":\n          return <VSelect label={item.name} hideDetails={true} items={['Lulu', 'frank', 'jack', 'joma', 'wang']}\n                          itemTitle={'label'}\n                          itemValue={'value'} density={item.config.density}\n                          variant={item.config.variant}></VSelect>\n        case \"role\":\n          return <VSelect label={item.name} hideDetails={true}\n                          items={['admin', 'tester', 'developer', 'frontend', 'backend']}\n                          itemTitle={'label'}\n                          itemValue={'value'} density={item.config.density}\n                          variant={item.config.variant}></VSelect>\n        case \"upload\":\n          return <VFileInput label={item.name} hideDetails={true} prependIcon={''} prependInnerIcon={'mdi-paperclip'}\n                             variant={item.config.variant}\n                             chips\n                             density={item.config.density}\n                             multiple={true}></VFileInput>\n        case \"radio\":\n\n          return <VRadioGroup label={item.name} density={item.config.density}\n                              hideDetails={true}>{item.config.options.map((o: formOption) => {\n            return <VRadio label={o.label} value={o.value}></VRadio>\n          })}</VRadioGroup>\n\n        case \"flexRow\":\n          return flexRawRender(item)\n        default:\n          return <span></span>\n      }\n    }\n\n    return () =>\n      <VCol class={'pa-0 d-flex flex-row align-center h-100'} cols={item.value.config.cols}>\n        <div\n          class={['py-1 form-item rounded flex-grow-1',\n            active.value?.id === item.value.id && 'form-item-active',\n            !props.preview && 'cursor-move px-1'\n          ]}\n          onClick={(event: any) => {\n            event.stopPropagation()\n            emitSelected(item.value)\n          }}>\n          {active.value?.id === item.value.id && !props.preview && <div class={'form-item-active-options'}>\n            <VChip size={'small'} color={'primary'} variant={'elevated'}\n                   onClick={() => emitDuplicate(item.value)}>duplicate</VChip>\n            <VChip size={'small'} color={'error'} variant={'elevated'}\n                   onClick={() => emitDelete(item.value)}>delete</VChip>\n          </div>}\n          {item.value.type === 'flexRow' && !props.preview &&\n            <span class={'form-item-active-flex'}>FlexRow</span>\n          }\n          {switchRender(item.value)}\n        </div>\n      </VCol>\n  }\n})\n\n\n"
  },
  {
    "path": "src/views/form/design/components/formitemEdit.tsx",
    "content": "import {defineComponent} from 'vue'\nimport {\n  VTextField,\n  VBtn,\n  VSwitch,\n  VColorPicker,\n  VIcon,\n  VSlider,\n  VSelect\n} from 'vuetify/components'\n\nimport {PropType} from \"vue\";\n\nexport default defineComponent({\n  props: {\n    'modelValue': {\n      required: true,\n      type: Object as PropType<formComponent>,\n      default: () => {\n      }\n    },\n  },\n  emits: ['update:modelValue'],\n  setup(props, {emit}) {\n\n    const optionsRender = (item: formComponent) => {\n      return <div class={['d-flex flex-column']}>\n        {item.config.options.map((o: formOption, index: number) => {\n          return <div class={['d-flex flex-row position-relative']}>\n            <VIcon {...{\n              'onClick': () => {\n                item.config.options.splice(index, 1)\n              }\n            }} class={'position-absolute cursor-pointer pa-1'} style={'right:-10px;top:-10px'} size={'small'}\n                   color={'error'}\n                   icon={\"mdi-close-circle-outline\"}></VIcon>\n            <VTextField label={'label'} class={'mr-1'} variant={'outlined'}\n                        density={'comfortable'} v-model={o.label}\n            ></VTextField>\n            <VTextField label={'value'} variant={'outlined'} density={'comfortable'} v-model={o.value}\n            ></VTextField>\n          </div>\n        })}\n        <VBtn color={'primary'} {...{\n          'onClick': () => {\n            item.config.options.push({})\n          }\n        }}>New Option</VBtn>\n      </div>\n    }\n\n    const variantSelectRender = (item: formComponent) => {\n      return <VSelect items={[\"filled\", \"outlined\", \"plain\", \"underlined\", \"solo\"]}\n                      density={'comfortable'}\n                      variant={'outlined'}\n                      label={'variant'}\n                      v-model={item.config.variant}></VSelect>\n    }\n    const densitySelectRender = (item: formComponent) => {\n      return <VSelect items={['default', 'comfortable', 'compact']}\n                      density={'comfortable'}\n                      variant={'outlined'}\n                      label={'density'}\n                      v-model={item.config.density}></VSelect>\n    }\n\n    const switchRender = (item: formComponent) => {\n      switch (item.type) {\n        case \"button\":\n          return <VColorPicker v-model={item.config.color} mode={\"hexa\"}></VColorPicker>\n        case \"checkbox\":\n          return optionsRender(item)\n        case \"radio\":\n          return optionsRender(item)\n        case \"switch\":\n          return <VTextField label={item.name} hideDetails={true} variant={item.config.variant}></VTextField>\n        case \"textField\":\n          return [variantSelectRender(item)]\n        case \"textArea\":\n          return undefined\n        case \"date\":\n          return undefined\n        case \"time\":\n          return undefined\n        case \"number\":\n          return undefined\n        case \"select\":\n          return optionsRender(item)\n        case \"upload\":\n          return <VTextField type={'number'} variant={'outlined'} density={'comfortable'}\n                             label={'MaxSize/MB'}></VTextField>\n\n        default:\n          return undefined\n      }\n    }\n\n    return () =>\n      <div>\n        <VTextField variant={'outlined'} density={'comfortable'} label={\"Id\"} readonly={true}\n                    v-model={props.modelValue.id}></VTextField>\n        <VTextField variant={'outlined'} density={'comfortable'} label={\"type\"} readonly={true}\n                    v-model={props.modelValue.type}></VTextField>\n        <VTextField variant={'outlined'} density={'comfortable'} label={\"name\"}\n                    v-model={props.modelValue.name}></VTextField>\n        {densitySelectRender(props.modelValue)}\n        <VSlider thumbLabel={'always'} v-model={props.modelValue.config.cols} max={12} min={0} step={1}\n                 color={'primary'}></VSlider>\n        <VSwitch color={\"primary\"} label={\"required\"}></VSwitch>\n        {switchRender(props.modelValue)}\n      </div>\n\n  }\n})\n\n\n"
  },
  {
    "path": "src/views/form/design/components/rightpanel.vue",
    "content": "<template>\n\n  <div class=\"d-flex flex-row bg-primary\">\n    <div>hello</div>\n    <div>world</div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/form/design/demofom.json",
    "content": "[\n  {\n    \"name\": \"Applicant\",\n    \"value\": \"user\",\n    \"type\": \"user\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_9\"\n  },\n  {\n    \"name\": \"Begin Date\",\n    \"value\": \"date\",\n    \"type\": \"date\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_10\"\n  },\n  {\n    \"name\": \"End Date\",\n    \"value\": \"date\",\n    \"type\": \"date\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_11\"\n  },\n  {\n    \"name\": \"Amount of passenger\",\n    \"value\": \"number\",\n    \"type\": \"number\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_12\"\n  },\n  {\n    \"name\": \"Driver\",\n    \"value\": \"user\",\n    \"type\": \"user\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_13\"\n  },\n  {\n    \"name\": \"Describe reason\",\n    \"value\": \"textarea\",\n    \"type\": \"textArea\",\n    \"config\": {\n      \"variant\": \"outlined\"\n    },\n    \"id\": \"Formlgdaa59i_14\"\n  }\n]\n"
  },
  {
    "path": "src/views/form/design/form.d.ts",
    "content": "interface formComponentGroup {\n  name: string,\n  children: formComponent[]\n}\n\ninterface formComponent {\n  [key: string]: any,\n\n  id?: string,\n  name: string,\n  value: string,\n  type: 'textField' | 'number' | 'switch' | 'button' | 'textArea' | 'select' | 'date' | 'time' | 'checkbox' | 'upload' | 'user' | 'role' | 'flexRow' | 'radio'\n  config: formComponentConfig\n}\n\ntype formComponentConfig =\n  Record<string, any>\n  |\n  (\n    VSelect\n    & VTextField\n    & VBtn\n    & VCheckbox\n    & VRadio\n    & VCol\n    & {\n    options: formOption[],\n    formChildren: formComponent[]\n  }\n    )\n\n\ninterface formOption {\n  label?: string,\n  value?: string\n}\n\ntype formComponentType = NonNullable<formComponent['type']>\n\n\ninterface form {\n  id: string,\n  name: string,\n}\n"
  },
  {
    "path": "src/views/form/design/formComponents.ts",
    "content": "const formComponents: (primary: string) => formComponentGroup[] = (primary: string) => [\n  {\n    name: 'Base',\n    children: [\n      {\n        name: 'textField',\n        value: 'textField',\n        type: 'textField',\n        config:\n          {\n            variant: 'outlined',\n          }\n      },\n      {\n        name: 'button',\n        value: 'button',\n        type: 'button',\n        config: {\n          color: primary,\n        }\n      },\n      {\n        name: 'date',\n        value: 'date',\n        type: 'date',\n        config: {\n          variant: 'outlined',\n        }\n      },\n      {\n        name: 'time',\n        value: 'time',\n        type: 'time',\n        config: {\n          variant: 'outlined',\n        }\n      },\n      {\n        name: 'number',\n        value: 'number',\n        type: 'number',\n        config: {\n          variant: 'outlined',\n        }\n      },\n      {\n        name: 'textarea',\n        value: 'textarea',\n        type: 'textArea',\n        config: {\n          variant: 'outlined',\n        }\n      },\n      {\n        name: 'upload',\n        value: 'upload',\n        type: 'upload',\n        config: {\n          variant: 'outlined',\n        }\n      },\n      {\n        name: 'select',\n        value: 'select',\n        type: 'select',\n        config: {\n          variant: 'outlined',\n          options: [\n            {\n              label: 'option1',\n              value: 'option1'\n            },\n            {\n              label: 'option2',\n              value: 'option2'\n            }\n          ]\n        }\n      },\n      {\n        name: 'checkbox',\n        value: 'checkbox',\n        type: 'checkbox',\n        config: {\n          options: [\n            {\n              label: 'option1',\n              value: 'option1'\n            },\n\n            {\n              label: 'option2',\n              value: 'option2'\n            }\n          ]\n        }\n      },\n      {\n        name: 'radio',\n        value: 'radio',\n        type: 'radio',\n        config: {\n          options: [\n            {\n              label: 'option1',\n              value: 'option1'\n            },\n\n            {\n              label: 'option2',\n              value: 'option2'\n            }\n          ]\n        }\n      }\n    ]\n  },\n  {\n    name: 'Advanced',\n    children: [\n      {\n        name: 'user',\n        value: 'user',\n        type: 'user',\n        config: {\n          variant: 'outlined'\n        }\n      },\n      {\n        name: 'role',\n        value: 'role',\n        type: 'role',\n        config: {\n          variant: 'outlined'\n        }\n      }\n    ]\n  },\n  {\n    name: 'Layout',\n    children: [\n      {\n        name: 'flexRow',\n        value: 'flexRow',\n        type: 'flexRow',\n        config: {\n          formChildren: []\n        }\n      }\n    ]\n  }\n]\n\nexport default formComponents\n"
  },
  {
    "path": "src/views/form/design/index.vue",
    "content": "<template>\n  <v-layout class=\"h-100\">\n    <v-navigation-drawer width=\"250\" :permanent=\"true\">\n      <v-list>\n        <v-list-item>\n          <div class=\"text-h5 font-weight-bold\">Form Component</div>\n        </v-list-item>\n        <v-list-item v-for=\"component in formComponents\">\n          <v-list-item-title>\n            {{ component.name }}\n          </v-list-item-title>\n          <v-list-item-action>\n            <div v-if=\"component.children\" class=\"component-list d-flex flex-row w-100  flex-wrap \">\n              <vue-draggable :list=\"component.children\" item-key=\"name\"\n                             :clone=\"cloneComponent\"\n                             @end=\"endComponent\"\n                             :sort=\"false\"\n                             :group=\"{ name: 'formComponentGroups', pull: 'clone', put: false }\">\n                <template #item=\"{ element }\">\n                  <v-chip label color=\"primary\" class=\"component-list-item cursor-move\" @click=\"addComponent(element)\">\n                    {{ element.name }}\n                  </v-chip>\n                </template>\n              </vue-draggable>\n            </div>\n          </v-list-item-action>\n        </v-list-item>\n      </v-list>\n    </v-navigation-drawer>\n    <v-navigation-drawer width=\"250\" :permanent=\"true\" location=\"right\">\n      <v-card elevation=\"0\">\n        <v-card-title>\n          Attribute\n        </v-card-title>\n        <v-card-text>\n          <formitem-edit v-if=\"!!activeComponent\" v-model=\"activeComponent\"></formitem-edit>\n        </v-card-text>\n      </v-card>\n    </v-navigation-drawer>\n    <v-main class=\"form-design\">\n      <v-card elevation=\"0\" class=\"h-100\">\n        <v-card-title class=\"d-flex align-center\">\n          <v-text-field density=\"comfortable\" hide-details\n                        variant=\"filled\"\n                        placeholder=\"formName\"\n                        v-model=\"form.name\"\n                        class=\"v-text-field-rounded pl-0 \"\n          ></v-text-field>\n\n          <v-spacer/>\n          <span class=\"text-caption\">{{ form.id }}</span>\n          <v-btn variant=\"tonal\" color=\"primary\" @click=\"showPreview()\"\n                 :disabled=\"selectedFormComponents.length===0\">Preview\n          </v-btn>\n          <v-btn variant=\"tonal\" color=\"error\" class=\"ml-1\" :disabled=\"selectedFormComponents.length===0\"\n                 @click=\"selectedFormComponents=[]\">ClearAll\n          </v-btn>\n        </v-card-title>\n        <v-card-text class=\"px-0\">\n          <v-form class=\"px-1\">\n            <div v-if=\"selectedFormComponents.length === 0\" class=\"px-1 text-h6 text-primary position-absolute\">\n              drag component from left panel\n              to here !\n            </div>\n            <vue-draggable\n              :list=\"selectedFormComponents\"\n              class=\"draggable-area\"\n              :animation=\"340\"\n              group=\"formComponentGroups\"\n              ghost-class=\"ghost\"\n              item-key=\"id\"\n            >\n              <template #item=\"{ element }\">\n                <formitem :item=\"element\"\n                          :active=\"activeComponent\"\n                          :list=\"selectedFormComponents\"\n                          :id-prefix=\"form.id\"\n                          @delete=\"deleteComponent\"\n                          @duplicate=\"duplicateComponent\"\n                          @selected=\"selectComponent\"\n                >\n                </formitem>\n              </template>\n            </vue-draggable>\n\n          </v-form>\n        </v-card-text>\n      </v-card>\n    </v-main>\n    <preview v-model=\"previewDialog\" :components=\"selectedFormComponents\" :form=\"form\"></preview>\n  </v-layout>\n</template>\n\n<script setup lang=\"ts\">\nimport VueDraggable from 'vuedraggable'\nimport {ref, reactive} from 'vue'\nimport Formitem from \"@/views/form/design/components/formitem\";\nimport {cloneDeep, uniqueId} from 'lodash-es'\nimport formComponentsGetter from \"./formComponents\";\nimport FormitemEdit from \"@/views/form/design/components/formitemEdit\";\nimport Preview from \"@/views/form/design/preview\";\nimport FromDemo from './demofom.json'\n\nconst form = reactive<form>({\n  id: 'Form' + (+new Date).toString(36).slice(-10),\n  name: 'Make Car Use Request'\n})\nconst activeComponent = ref<formComponent>()\n\nconst previewDialog = ref<boolean>(false)\nconst theme = useTheme();\nconst formComponents = formComponentsGetter(theme.current.value.colors.primary)\nconst cloningComponent = ref<formComponent>()\nconst demo = FromDemo as formComponent[]\nconst selectedFormComponents = ref<Array<formComponent>>(demo)\nconst selectComponent = (c: formComponent) => {\n  activeComponent.value = c\n}\nif (selectedFormComponents.value.length > 0) {\n  selectComponent(selectedFormComponents.value[0])\n}\nconst endComponent = (c: { to: any, from: any }) => {\n  if (c.from != c.to) {\n    cloningComponent.value && selectComponent(cloningComponent.value)\n  }\n}\nconst addComponent = (c: formComponent) => {\n  c = cloneComponent(c)\n  cloningComponent.value = c\n  activeComponent.value = cloningComponent.value\n  selectedFormComponents.value.push(activeComponent.value)\n}\nconst cloneComponent = (c: formComponent) => {\n  const clone = cloneDeep(c)\n  clone.id = form.id + '_' + uniqueId()\n  cloningComponent.value = clone\n  return clone\n}\n\nconst deleteComponent = (c: formComponent) => {\n}\nconst duplicateComponent = (c: formComponent) => {\n}\n\nconst showPreview = () => {\n  previewDialog.value = true\n  console.log(JSON.stringify(selectedFormComponents.value))\n}\n\n</script>\n\n<style scoped lang=\"scss\">\n\n.component-list {\n  margin-right: -8px;\n  margin-bottom: -8px;\n\n  .component-list-item {\n    margin-right: 8px;\n    margin-bottom: 8px;\n  }\n}\n\n.form-design {\n  &:deep(.ghost) {\n    position: relative;\n    display: block;\n    min-height: 60px;\n    flex-grow: 1;\n    flex-basis: 0;\n    //height: 60px;\n\n    &::before {\n      content: \" \";\n      position: absolute;\n      left: 0;\n      right: 0;\n      top: 0;\n      height: 4px;\n      background: rgb(var(--v-theme-primary));\n      z-index: 2;\n    }\n  }\n\n  .draggable-area {\n\n    min-height: 500px;\n\n    &:deep(.form-item) {\n      position: relative;\n      margin-bottom: 8px;\n      border: 1px dashed #ccc;\n\n\n      .form-item-active-options {\n        z-index: 10;\n        position: absolute;\n        right: 0;\n        top: -10px;\n      }\n\n      .form-item-active-flex {\n        z-index: 10;\n        position: absolute;\n        left: 0;\n        top: -10px\n      }\n    }\n\n    &:deep(.form-item:last-child) {\n      margin-bottom: 0;\n    }\n\n    &:deep(.form-item-active) {\n      background-color: rgb(var(--v-theme-primary), 0.1);\n      border: none;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/form/design/preview.tsx",
    "content": "import {defineComponent, computed, ref, PropType} from 'vue'\nimport {\n  VDialog, VCard, VCardText, VCardTitle, VCardActions,\n  VBtn\n} from \"vuetify/components\";\nimport Formitem from \"@/views/form/design/components/formitem\";\nimport {VSpacer} from \"vuetify/components/VGrid\";\n\nexport default defineComponent({\n  props: {\n    'modelValue': {\n      required: true,\n      type: Boolean,\n      default: () => {\n      }\n    },\n    form: {\n      required: true,\n      type: Object as PropType<form>,\n    },\n    components: {\n      required: true,\n      type: Array as PropType<formComponent[]>\n    }\n  },\n  setup(props, {emit}) {\n    const m = ref<Boolean>(false)\n    const model = computed({\n      set(v: Boolean) {\n        m.value = v\n        emit('update:modelValue', v)\n      },\n      get() {\n        return props.modelValue\n      }\n    })\n\n    return () => <VDialog maxWidth={'600px'} v-model={model.value}>\n      <VCard>\n        <VCardTitle>{props.form.name || props.form.id}</VCardTitle>\n        <VCardText>\n          {props.components.map(c => {\n            return <Formitem\n              preview={true}\n              item={c}></Formitem>\n          })}\n        </VCardText>\n        <VCardActions>\n          <VSpacer></VSpacer>\n          <VBtn {...{onClick: () => model.value = false}} variant={'tonal'} color={'error'}>cancel</VBtn>\n        </VCardActions>\n      </VCard>\n    </VDialog>\n  }\n})\n"
  },
  {
    "path": "src/views/form/list.vue",
    "content": "<template>\n  <div class=\"d-flex flex-column flex-grow-1\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <div class=\"text-h4\">Forms</div>\n        <breadcrumb/>\n      </div>\n      <v-spacer></v-spacer>\n      <v-btn class=\"text-capitalize\" color=\"primary\">\n        Create Form\n      </v-btn>\n    </div>\n\n    <v-card>\n      <!-- items list -->\n      <v-row dense class=\"pa-2 align-center\">\n        <v-col cols=\"6\">\n          <v-menu offset-y left>\n            <template v-slot:activator=\"{ props}\">\n              <v-slide-x-reverse-transition mode=\"out-in\">\n                <v-btn v-show=\"selected.length > 0\" v-bind=\"props\">\n                  Actions\n                  <v-icon right>mdi-menu-down</v-icon>\n                </v-btn>\n              </v-slide-x-reverse-transition>\n            </template>\n            <v-list dense>\n              <v-list-item>\n                <v-list-item-title>Verify</v-list-item-title>\n              </v-list-item>\n              <v-list-item>\n                <v-list-item-title>Disable</v-list-item-title>\n              </v-list-item>\n              <v-divider></v-divider>\n              <v-list-item>\n                <v-list-item-title>Delete</v-list-item-title>\n              </v-list-item>\n            </v-list>\n          </v-menu>\n\n        </v-col>\n        <v-col cols=\"6\" class=\"d-flex text-right align-center\">\n          <v-text-field\n            v-model=\"searchQuery\"\n            append-inner-icon=\"mdi-magnify\"\n            class=\"flex-grow-1 mr-md-2\"\n            variant=\"solo\"\n            density=\"comfortable\"\n            hide-details\n            clearable\n            placeholder=\"e.g. filter for id, email, name, etc\"\n          ></v-text-field>\n          <v-btn\n            :loading=\"loading\"\n            @click=\"getTableData()\"\n            icon\n            flat\n            small\n            class=\"ml-2\"\n          >\n            <v-icon>mdi-refresh</v-icon>\n          </v-btn>\n        </v-col>\n      </v-row>\n\n      <v-data-table-server\n        v-model=\"selected\"\n        show-select\n        :headers=\"headers\"\n        :items=\"items\"\n        :search=\"searchQuery\"\n        :loading=\"loading\"\n        :items-length=\"total\"\n        :items-per-page=\"pageSize\"\n        @update:options=\"options = $event\"\n        class=\"flex-grow-1\"\n      >\n        <template v-slot:item.id=\"{ item  : {raw} }\">\n          <div class=\"font-weight-bold\">#\n            <copy-label :text=\"raw.id + ''\"/>\n          </div>\n        </template>\n\n        <template v-slot:item.status=\"{item:{raw}}\">\n          {{ formStatusLabels [(raw as FormManagement.Form ).status] }}\n        </template>\n\n        <template v-slot:item.created=\"{ item  : {raw} }\">\n          <div>{{ raw.created }}</div>\n        </template>\n\n        <template v-slot:item.action=\"{item:{raw} }\">\n          <div class=\"actions\">\n            <v-btn flat icon :to=\"`/apps/manager-user/edit/${raw.id}`\">\n              <v-icon>mdi-open-in-new</v-icon>\n            </v-btn>\n          </div>\n        </template>\n      </v-data-table-server>\n    </v-card>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n\nimport {Ref, ref} from \"vue\";\nimport {useLoading} from '@/hooks';\nimport {fetchFormList} from \"@/service\";\nimport {formStatusLabels} from '@/constants'\n\nconst {loading, startLoading, endLoading} = useLoading(true);\n\nconst options = ref()\nconst total = ref(0)\nconst pageSize = ref(10)\nconst items = ref<Array<FormManagement.Form>>([])\nconst searchQuery = ref('')\nconst selected = ref<Array<FormManagement.Form>>([])\nconst headers: Ref<DataTableHeader> = ref<DataTableHeader>([\n  {title: 'Id', align: 'start', key: 'id'},\n  {title: 'Name', key: 'name'},\n  {title: 'Status', key: 'status'},\n  {title: 'Created', align: 'start', key: 'created'},\n  {title: '', sortable: false, align: 'end', key: 'action'}\n])\n\nasync function getTableData() {\n  startLoading();\n  const {data} = await fetchFormList();\n  endLoading();\n  if (data) {\n    total.value = data.total\n    items.value = data.list\n  }\n}\n\nasync function init() {\n  await getTableData()\n  watch(options, (n, o) => {\n    getTableData()\n  }, {deep: true})\n}\n\ninit()\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/index.ts",
    "content": "import type {RouteComponent} from 'vue-router';\n\nexport const views: Record<PageRoute.LastDegreeRouteKey,\n  RouteComponent | (() => Promise<{ default: RouteComponent }>)> = {\n  404: () => import('./_builtin/error/NotFoundPage.vue'),\n  403: () => import('./_builtin/error/NotFoundPage.vue'),\n  500: () => import('./_builtin/error/UnexpectedPage.vue'),\n  login: () => import('./_builtin/auth/index.vue'),\n  'not-found': () => import('./_builtin/error/NotFoundPage.vue'),\n  'dashboard_analytics': () => import('./dashboard/index.vue'),\n  \"apps_manager-user_list\": () => import('./users/UsersPage.vue'),\n  \"apps_manager-user_edit\": () => import('./users/EditUserPage.vue'),\n  \"apps_board\": () => import('./board/pages/BoardPage.vue'),\n  \"apps_todo_tasks\": () => import('@/views/todo/pages/TasksPage.vue'),\n  \"apps_todo_completed\": () => import('@/views/todo/pages/CompletedPage.vue'),\n  \"apps_todo_label\": () => import('@/views/todo/pages/LabelPage.vue'),\n  \"apps_chat-channel\": () => import('./chart/ChatChannel.vue'),\n  \"pages_error_notfound\": () => import('./_builtin/error/NotFoundPage.vue'),\n  \"pages_error_unexpected\": () => import('./_builtin/error/UnexpectedPage.vue'),\n  \"other_menu-levels-2-1\": () => import('./menulevels/lv2.1.vue'),\n  \"other_menu-levels-3-1\": () => import('./menulevels/lv3.1.vue'),\n  \"other_menu-levels-3-2\": () => import('./menulevels/lv3.2.vue'),\n  \"flowable_design\": () => import('./flowable/design/index.vue'),\n  \"form_list\": () => import('./form/list.vue'),\n  \"form_design\": () => import('./form/design/index.vue'),\n};\n"
  },
  {
    "path": "src/views/menulevels/lv2.1.vue",
    "content": "<template>\n  <div class=\"flex-grow-1\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <breadcrumb :root=\"'other'\"/>\n        <div class=\"text-h4\">Menu leve2.1</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/menulevels/lv3.1.vue",
    "content": "<template>\n  <div class=\"flex-grow-1\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <breadcrumb :root=\"'other'\"/>\n        <div class=\"text-h4\">Menu level3.1</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/menulevels/lv3.2.vue",
    "content": "<template>\n  <div class=\"flex-grow-1\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <breadcrumb :root=\"'other'\"/>\n        <div class=\"text-h4\">Menu level3.2</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\n\n</script>\n\n<style scoped>\n\n</style>\n"
  },
  {
    "path": "src/views/todo/TodoLayout.vue",
    "content": "<template>\n  <v-layout full-height :class=\"{'position-static':!lgAndUp}\">\n    <div class=\"d-flex flex-grow-1 flex-row\">\n      <v-navigation-drawer\n        v-model=\"drawer\"\n        :permanent=\"lgAndUp\"\n        floating\n        :class=\"{'top-z-index':!lgAndUp}\"\n        class=\"elevation-1 rounded flex-shrink-0\"\n      >\n        <todo-menu class=\"todo-app-menu pa-2\" @open-compose=\"openCompose\"></todo-menu>\n      </v-navigation-drawer>\n\n\n      <v-main>\n        <div class=\"d-flex flex-grow-1 h-100 flex-column\" :class=\"{'pl-3':lgAndUp}\">\n            <v-toolbar class=\"hidden-lg-and-up flex-grow-0 mb-2\" elevation=\"1\" rounded color=\"surface\">\n              <v-app-bar-nav-icon @click=\"drawer = !drawer\"></v-app-bar-nav-icon>\n              <div class=\"title font-weight-bold\">Todo App</div>\n            </v-toolbar>\n          <router-view class=\"flex-grow-1\"/>\n        </div>\n      </v-main>\n\n      <todo-compose ref=\"compose\"/>\n    </div>\n  </v-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport TodoCompose from '@/views/todo/components/TodoCompose.vue'\nimport TodoMenu from './components/TodoMenu.vue'\nimport {useDisplay} from \"vuetify\";\n\nconst {lgAndUp} = useDisplay()\n\nconst drawer = ref()\nconst compose = ref<InstanceType<typeof TodoCompose> | null>()\nconst openCompose = (task: Todo.Task) => {\n  compose.value?.open(task)\n}\n</script>\n<style>\n\n</style>\n"
  },
  {
    "path": "src/views/todo/components/TodoCompose.vue",
    "content": "<template>\n  <v-dialog v-model=\"dialog\" width=\"600\">\n    <v-card>\n      <v-card-title class=\"pa-2\">\n        {{ isEdit ? 'Edit Task' : 'Add Task' }}\n        <v-spacer></v-spacer>\n        <v-btn icon  variant=\"plain\"  @click=\"dialog = false\">\n          <v-icon>mdi-close</v-icon>\n        </v-btn>\n      </v-card-title>\n\n      <v-divider></v-divider>\n\n      <!-- task form -->\n      <div>\n        <v-text-field\n          v-model=\"title\"\n          class=\"px-2 py-1 v-field-flat\"\n          variant=\"solo\"\n          :placeholder=\"$t('common.title')\"\n          autofocus\n          hide-details\n          @keyup.enter=\"save\"\n        ></v-text-field>\n\n        <v-divider></v-divider>\n\n        <v-textarea\n          v-model=\"description\"\n          class=\"px-2 py-1 v-field-flat\"\n          variant=\"solo\"\n          :placeholder=\"$t('common.description')\"\n          hide-details\n        ></v-textarea>\n\n        <v-select\n          v-model=\"taskLabels\"\n          class=\"px-2 my-3 v-field-flat\"\n          :items=\"labels\"\n          placeholder=\"Labels\"\n          item-value=\"id\"\n          chips\n          multiple\n          clearable\n          variant=\"solo\"\n        >\n          <template v-slot:chip=\"{ item:{raw} }\">\n            <v-chip\n              :color=\"raw.color\"\n              label\n              variant=\"outlined\"\n            >\n              {{ raw.title }}\n            </v-chip>\n          </template>\n        </v-select>\n      </div>\n\n      <v-divider></v-divider>\n\n      <v-card-actions class=\"pa-2\">\n        <v-btn outlined @click=\"close\">{{ $t('common.cancel') }}</v-btn>\n        <v-spacer></v-spacer>\n        <v-btn color=\"primary\" @click=\"save\">{{ $t('common.save') }}</v-btn>\n      </v-card-actions>\n    </v-card>\n  </v-dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref} from 'vue'\nimport {useTodoStore} from \"@/views/todo/store\";\n\nconst dialog = ref(false)\nconst title = ref('')\nconst description = ref('')\nconst taskRef = ref<Todo.Task>()\nconst taskLabels = ref<Array<string>>([])\n\nconst isEdit = computed(() => {\n  return taskRef.value && taskRef.value?.id\n})\nconst open = (task: Todo.Task) => {\n  if (task) {\n    taskRef.value = task\n    title.value = taskRef.value?.title\n    description.value = taskRef.value?.description\n    taskLabels.value = taskRef.value?.labels\n  } else {\n    title.value = ''\n    description.value = ''\n    taskLabels.value = []\n  }\n  dialog.value = true\n}\ndefineExpose({\n  open,\n})\nconst close = () => {\n  dialog.value = false\n}\nconst save = () => {\n  const t: Todo.Task = {\n    title: title.value,\n    description: description.value,\n    labels: taskLabels.value,\n    id: '',\n    completed: false\n  }\n  if (taskRef.value?.id) {\n    updateTask({\n      ...taskRef.value,\n      ...t,\n    })\n  } else {\n    addTask(t)\n  }\n\n  close()\n}\n\nconst {updateTask, addTask, labels} = useTodoStore()\n\n</script>\n"
  },
  {
    "path": "src/views/todo/components/TodoList.vue",
    "content": "<template>\n  <v-card min-height=\"380px\">\n    <v-text-field\n      v-model=\"filter\"\n      class=\"todo-filter px-1 py-2 v-field-flat\"\n      placeholder=\"Filter tasks\"\n      prepend-inner-icon=\"mdi-magnify\"\n      variant=\"solo\"\n      clearable\n      color=\"red\"\n      hide-details\n    ></v-text-field>\n    <v-divider></v-divider>\n\n    <div v-if=\"tasks.length === 0\">\n      <div class=\"px-1 py-6 text-center\">All done! No more tasks!</div>\n    </div>\n\n\n    <v-slide-y-reverse-transition\n      v-else\n      group\n      tag=\"div\"\n    >\n      <div v-for=\"(task,index) in visibleTasks\" :key=\"index\" class=\"d-flex pa-2 task-item align-center\"\n           @click=\"$emit('edit-task', task)\">\n        <v-checkbox-btn\n          :input-value=\"task.completed\"\n          hide-details\n          false-icon=\"mdi-checkbox-blank-circle-outline\"\n          true-icon=\"mdi-checkbox-marked-circle\"\n        >\n        </v-checkbox-btn>\n\n        <div class=\"task-item-content flex-grow-1\" :class=\"{ 'complete': task.completed }\">\n          <div class=\"font-weight-bold\" v-text=\"task.title\"></div>\n          <div v-text=\"task.description\"></div>\n          <div>\n            <v-chip\n              v-for=\"label in task.labels\"\n              :key=\"label\"\n              :color=\"getLabelColor(label)\"\n              class=\"font-weight-bold mt-1 mr-1\"\n              size=\"x-small\"\n\n            >\n              {{ getLabelTitle(label) }}\n            </v-chip>\n          </div>\n        </div>\n        <div class=\"d-flex align-center\">\n          <v-btn icon flat>\n            <v-icon>mdi-delete-outline</v-icon>\n          </v-btn>\n        </div>\n      </div>\n    </v-slide-y-reverse-transition>\n  </v-card>\n</template>\n\n<script lang=\"ts\" setup>\nimport {PropType} from \"vue\";\nimport {useTodoStore} from \"@/views/todo/store\";\n\nconst {labels} = useTodoStore()\n\nconst props = defineProps({\n  // task list\n  tasks: {\n    type: Array as PropType<Array<Todo.Task>>,\n    default: () => []\n  }\n})\nconst filter = ref(\"\")\n\nconst visibleTasks = computed(() => {\n  if (!filter.value) {\n    return props.tasks\n  }\n  return props.tasks?.filter(i => i.title.includes(filter.value))\n})\n\nconst getLabelColor = (id: string) => {\n  const label = labels.find((l) => l.id === id)\n  return label ? label.color : ''\n}\nconst getLabelTitle = (id: string) => {\n  const label = labels.find((l) => l.id === id)\n  return label ? label.title : ''\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.todo-filter {\n  position: sticky;\n  background-color: var(--v-background-base) !important;\n  z-index: 2;\n  top: 0;\n}\n\n\n.task-item {\n  cursor: pointer;\n  border-bottom: 1px solid rgba(100, 100, 100, 0.1);\n\n  &:hover {\n    background-color: rgba(100, 100, 100, 0.1);\n  }\n\n  .task-item-content {\n    &.complete {\n      text-decoration: line-through;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/views/todo/components/TodoMenu.vue",
    "content": "<template>\n  <div>\n    <v-btn\n      block\n      size=\"large\"\n      color=\"primary\"\n      class=\"mb-3 text-body-2\"\n      @click=\"$emit('open-compose')\"\n    >{{ $t('todo.addTask') }}\n    </v-btn>\n\n    <v-list nav class=\"mt-2 pa-0\">\n      <v-list-item to=\"/apps/todo/tasks\" active-class=\"primary--text\" link>\n        <template #prepend>\n          <v-icon size=\"small\" >mdi-checkbox-marked-circle-outline</v-icon>\n        </template>\n        <v-list-item-title>{{ $t('todo.tasks') }}</v-list-item-title>\n        <template #append>\n          <v-list-item-action v-if=\"incompleteTasks.length > 0\">\n            <v-badge inline color=\"primary\" class=\"font-weight-bold\" :content=\"incompleteTasks.length\">\n            </v-badge>\n          </v-list-item-action>\n        </template>\n      </v-list-item>\n\n      <v-list-item to=\"/apps/todo/completed\" active-class=\"primary--text\" link>\n        <template #prepend>\n          <v-icon  size=\"small\">mdi-check</v-icon>\n        </template>\n        <v-list-item-title>{{ $t('todo.completed') }}</v-list-item-title>\n      </v-list-item>\n    </v-list>\n\n    <v-list dense nav class=\"mt-2 pa-0\">\n      <div class=\"overline pa-1 mt-2\">{{ $t('todo.labels') }}</div>\n\n      <v-list-item\n        v-for=\"label in labels\"\n        :key=\"label.id\"\n        :to=\"`/apps/todo/label/${label.id}`\"\n        exact\n        active-class=\"primary--text\"\n        link\n      >\n        <template #prepend>\n          <v-icon  size=\"small\" :color=\"label.color\">mdi-label-outline</v-icon>\n        </template>\n        <v-list-item-title>{{ label.title }}</v-list-item-title>\n      </v-list-item>\n    </v-list>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\n\nimport {useTodoStore} from \"@/views/todo/store\";\n\nconst {labels, incompleteTasks} = storeToRefs(useTodoStore())\n\n</script>\n"
  },
  {
    "path": "src/views/todo/pages/CompletedPage.vue",
    "content": "<template>\n  <todo-list :tasks=\"completeTasks\" @edit-task=\"$emit('edit-task', $event)\"/>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useTodoStore} from \"@/views/todo/store\";\nimport TodoList from '../components/TodoList.vue'\nconst {completeTasks} = storeToRefs(useTodoStore())\n\n</script>\n"
  },
  {
    "path": "src/views/todo/pages/LabelPage.vue",
    "content": "<template>\n  <todo-list :tasks=\"labelTasks\" @edit-task=\"$emit('edit-task', $event)\"/>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useTodoStore} from \"../store\";\nimport {useRouter, useRoute} from \"vue-router\";\nimport {computed, ref} from 'vue'\nimport TodoList from '../components/TodoList.vue'\n\nconst route = useRoute()\n\nconst {taskList: tasks} = useTodoStore()\nconst {currentRoute} = useRouter()\nconst labelId = ref(currentRoute.value.params['id'] as string)\n\n\nwatch(() => route.params['id'], (nId) => {\n  labelId.value = (nId as string)\n})\n\nconst labelTasks = computed(() => {\n  return tasks\n    .filter((t) => t.labels.indexOf(labelId.value) !== -1)\n})\n</script>\n"
  },
  {
    "path": "src/views/todo/pages/TasksPage.vue",
    "content": "<template>\n  <todo-list :tasks=\"incompleteTasks\" @edit-task=\"$emit('edit-task', $event)\"/>\n</template>\n\n<script setup lang=\"ts\">\nimport {useTodoStore} from \"@/views/todo/store\";\nimport TodoList from '@/views/todo/components/TodoList.vue'\n\nconst {incompleteTasks} = storeToRefs(useTodoStore())\n</script>\n"
  },
  {
    "path": "src/views/todo/store/content.ts",
    "content": "const taskList:Array<Todo.Task> = [{\n  id: 1,\n  title: 'find the report on one winged airplanes',\n  description: '✈️ one wing is the way to go',\n  labels: ['work'],\n  completed: false\n}, {\n  id: 2,\n  title: '🤘 get marshmallows for camping',\n  description: 'we need it for reasons 🤤',\n  labels: ['groceries'],\n  completed: false\n}, {\n  id: 3,\n  title: '🏄‍♀️ book surf lessons for September',\n  description: '',\n  labels: ['fun'],\n  completed: false\n}, {\n  id: 4,\n  title: 'Take my car to the shop',\n  description: 'the brakes are broken',\n  labels: [],\n  completed: true\n}, {\n  id: 5,\n  title: 'Choose a pool 🏊‍♂️ from the catalog',\n  description: 'must fit the whole family',\n  labels: ['fun', 'groceries'],\n  completed: false\n}]\nconst labels:Array<Todo.Label>= [{\n  id: 'work',\n  title: 'Work',\n  color: 'primary'\n}, {\n  id: 'fun',\n  title: 'Fun',\n  color: 'blue'\n}, {\n  id: 'groceries',\n  title: 'Groceries',\n  color: 'orange'\n}]\nexport {labels, taskList}\n"
  },
  {
    "path": "src/views/todo/store/index.ts",
    "content": "import {defineStore} from \"pinia\";\nimport {taskList, labels} from './content'\n\ninterface TodoState {\n  taskList: Array<Todo.Task>\n  labels: Array<Todo.Label>\n}\n\nexport const useTodoStore = defineStore('todo-store', {\n  state: (): TodoState => ({\n    taskList,\n    labels\n  }),\n  getters:{\n    incompleteTasks({taskList}) {\n      return taskList.filter((t) => !t.completed)\n    },\n    completeTasks({taskList}) {\n      return taskList.filter((t) => t.completed)\n    }\n  },\n  actions: {\n    addTask(task: Todo.Task) {\n      this.taskList.push({\n        ...task,\n        completed: false,\n        id: '_' + Math.random().toString(36).substr(2, 9),\n      })\n    },\n    updateTask(task: Todo.Task) {\n      const taskIdx = this.taskList.find((t) => t.id === task.id)\n      if (taskIdx)\n        Object.assign(taskIdx, task)\n    },\n    taskCompleted(task: Todo.Task) {\n      const taskIdx = this.taskList.findIndex((t) => t.id === task.id)\n      this.taskList[taskIdx].completed = true\n    },\n    taskIncomplete(task: Todo.Task) {\n      const taskIdx = this.taskList.findIndex((t) => t.id === task.id)\n      this.taskList[taskIdx].completed = false\n    },\n    deleteTask(task: Todo.Task) {\n      const taskIdx = this.taskList.findIndex((t) => t.id === task.id)\n\n      if (taskIdx !== -1) this.taskList.splice(taskIdx, 1)\n    }\n  }\n})\n"
  },
  {
    "path": "src/views/todo/typs/index.d.ts",
    "content": "declare namespace Todo {\n  interface Task{\n    id: number|string,\n    title: string,\n    description: string,\n    labels: Array<string>,\n    completed: boolean\n  }\n  interface Label{\n    id:string,\n    title:string,\n    color:string\n  }\n}\n"
  },
  {
    "path": "src/views/users/EditUser/AccountTab.vue",
    "content": "<template>\n  <div class=\"my-2\">\n    <div>\n      <v-card v-if=\"user.userStatus=='1'\" class=\"bg-warning mb-4\" light>\n        <v-card-title>User Disabled</v-card-title>\n        <v-card-subtitle>This user has been disabled! Login accesss has been revoked.</v-card-subtitle>\n        <v-card-text>\n          <v-btn theme=\"dark\" @click=\"user.userStatus = '1'\">\n            <v-icon left size=\"small\">mdi-account-check</v-icon>\n            Enable User\n          </v-btn>\n        </v-card-text>\n      </v-card>\n\n      <v-card>\n        <v-card-title>Basic Information</v-card-title>\n        <v-card-text>\n          <div class=\"d-flex flex-column flex-sm-row\">\n            <div>\n              <v-avatar size=\"95\" class=\"d-flex bg-blue-grey-lighten-4 rounded elevation-3 ma-0\">\n                <svg-icon :name=\"user.avatar\"></svg-icon>\n              </v-avatar>\n              <v-btn class=\"mt-1\" size=\"small\">Edit Avatar</v-btn>\n            </div>\n            <div class=\"flex-grow-1 pt-2 pa-sm-2\">\n              <v-text-field v-model=\"user.name\" label=\"Display name\"\n                            placeholder=\"name\"></v-text-field>\n              <v-text-field v-model=\"user.email\" label=\"Email\" hide-details></v-text-field>\n\n              <div class=\"d-flex flex-column\">\n                <v-checkbox v-model=\"user.verified\" density=\"comfortable\" label=\"Email Verified\"></v-checkbox>\n                <div>\n                  <v-btn\n                    v-if=\"!user.verified\"\n                  >\n                    <v-icon left small>mdi-email</v-icon>\n                    Send Verification Email\n                  </v-btn>\n                </div>\n              </div>\n\n              <div class=\"mt-2\">\n                <v-btn color=\"primary\">Save</v-btn>\n              </div>\n            </div>\n          </div>\n        </v-card-text>\n      </v-card>\n\n      <v-expansion-panels v-model=\"panel\" multiple class=\"user-panels mt-3\">\n        <v-expansion-panel value=\"actions\">\n          <v-expansion-panel-title class=\"title\">Actions</v-expansion-panel-title>\n          <v-expansion-panel-text>\n            <div class=\"mb-2\">\n              <div class=\"title\">Reset User Password</div>\n              <div class=\"subtitle mb-2\">Sends a reset password email to the user.</div>\n              <v-btn\n                class=\"mb-2\"\n              >\n                <v-icon left small>mdi-email</v-icon>\n                Send Reset Password Email\n              </v-btn>\n            </div>\n\n            <v-divider></v-divider>\n\n            <div class=\"my-2\">\n              <div class=\"title\">Export Account Data</div>\n              <div class=\"subtitle mb-2\">Export all the platform metadata for this user.</div>\n              <v-btn class=\"mb-2\">\n                <v-icon left small>mdi-clipboard-account</v-icon>\n                Export User Data\n              </v-btn>\n            </div>\n\n            <v-divider></v-divider>\n\n            <div class=\"my-2\">\n              <div class=\"error--text title\">Danger Zone</div>\n              <div class=\"subtitle mb-2\">Full administrator with access to this dashboard.</div>\n\n              <div class=\"my-2\">\n                <v-btn\n                  v-if=\"user.role === 'admin'\"\n                  color=\"primary\"\n                  @click=\"user.role = 'user'\"\n                >\n                  <v-icon left small>mdi-security</v-icon>\n                  Remove admin access\n                </v-btn>\n                <v-btn v-else color=\"primary\" @click=\"user.role = 'admin'\">\n                  <v-icon left small>mdi-security</v-icon>\n                  Set User as Admin\n                </v-btn>\n              </div>\n\n              <v-divider></v-divider>\n\n              <div class=\"subtitle mt-3 mb-2\">Prevent the user from signing in on the platform.</div>\n              <div class=\"my-2\">\n                <v-btn\n                  v-if=\"user.userStatus === '2'\"\n                  color=\"warning\"\n                >\n                  <v-icon left small>mdi-account-check</v-icon>\n                  Enable User\n                </v-btn>\n                <v-btn\n                  v-else\n                  color=\"warning\"\n                  @click=\"showDisableDialog(user)\"\n                >\n                  <v-icon left small>mdi-cancel</v-icon>\n                  Disable User\n                </v-btn>\n              </div>\n\n              <v-divider></v-divider>\n              <div\n                class=\"subtitle mt-3 mb-2\"\n              >To delete the user please transfer ownership or delete user's subscriptions.\n              </div>\n              <v-btn color=\"error\" @click=\"showDeleteDialog\">\n                <v-icon left small>mdi-delete</v-icon>\n                Delete User\n              </v-btn>\n            </div>\n          </v-expansion-panel-text>\n        </v-expansion-panel>\n        <v-expansion-panel value=\"metadata\">\n          <v-expansion-panel-title class=\"title\">Metadata</v-expansion-panel-title>\n          <v-expansion-panel-text class=\"body-2\">\n            <span class=\"font-weight-bold\">Created</span>\n            {{ user.created }}\n            <br/>\n            <span class=\"font-weight-bold\">Last Sign In</span>\n            {{ user.lastSignIn }}\n          </v-expansion-panel-text>\n        </v-expansion-panel>\n        <v-expansion-panel value=\"rawdata\">\n          <v-expansion-panel-title class=\"title\">Raw Data</v-expansion-panel-title>\n          <v-expansion-panel-text>\n            <pre class=\"body-2\">{{ user }}</pre>\n          </v-expansion-panel-text>\n        </v-expansion-panel>\n      </v-expansion-panels>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {PropType} from \"vue\";\n\ndefineProps({\n  user: {\n    type: Object as PropType<UserManagement.User>,\n    default: () => ({})\n  }\n})\nconst panel = ref(['metadata'])\n\nconst showDeleteDialog = () => {\n  const deleteDialog = window.$dialog?.show({\n    title: 'Delete User',\n    main: 'Are you sure you want to delete this user?',\n    confirm: () => {\n      deleteDialog?.confirmLoading(true)\n      setTimeout(() => {\n        deleteDialog?.confirmLoading(false)\n        window.$snackBar?.success('Action Completed!')\n        deleteDialog?.close()\n      }, 1000)\n    }\n  })\n}\nconst showDisableDialog = (user: UserManagement.User) => {\n  const disableDialog = window.$dialog?.show({\n    title: 'Disable User',\n    main: 'Are you sure you want to disable this user?',\n    confirmColor: 'warning',\n    confirm: () => {\n      disableDialog?.confirmLoading(true)\n      setTimeout(() => {\n        disableDialog?.confirmLoading(false)\n\n        user.userStatus = '2'\n\n        window.$snackBar?.success('Action Completed!')\n        disableDialog?.close()\n      }, 1000)\n    }\n  })\n}\n\n\n</script>\n<style lang=\"scss\">\n.user-panels .v-expansion-panel-title__overlay {\n  opacity: 0 !important;\n}\n</style>\n"
  },
  {
    "path": "src/views/users/EditUser/InformationTab.vue",
    "content": "<template>\n  <v-card class=\"my-2\">\n    <v-card-title>User Information</v-card-title>\n    <v-card-text>\n      <v-form>\n        <v-row>\n          <v-col cols=\"12\" md=\"6\">\n            <v-text-field v-model=\"user.address.detail\" label=\"Address Line 1\"></v-text-field>\n            <v-text-field v-model=\"user.address.zipCode\" label=\"Zip Code\"></v-text-field>\n            <v-text-field v-model=\"user.address.city\" label=\"City\"></v-text-field>\n            <v-text-field v-model=\"user.address.state\" label=\"State\"></v-text-field>\n            <v-text-field v-model=\"user.address.country\" label=\"Country\"></v-text-field>\n          </v-col>\n\n          <v-col cols=\"12\" md=\"6\">\n            <v-text-field v-model=\"user.phone\" label=\"Phone\"></v-text-field>\n            <v-text-field\n              v-model=\"user.birthDay\"\n              label=\"Birthday date\"\n              type=\"date\"\n            ></v-text-field>\n            <v-radio-group v-model=\"user.gender\" label=\"Gender\">\n              <v-radio v-for=\"g in genderOptions\" :label=\"g.label\" :value=\"g.value\"></v-radio>\n            </v-radio-group>\n          </v-col>\n        </v-row>\n\n        <div class=\"d-flex\">\n          <v-btn>Reset</v-btn>\n          <v-spacer></v-spacer>\n          <v-btn color=\"primary\">Save</v-btn>\n        </div>\n      </v-form>\n    </v-card-text>\n  </v-card>\n</template>\n\n<script setup lang=\"ts\">\nimport {PropType} from \"vue\";\nimport {genderOptions} from '@/constants';\n\nconst props = defineProps({\n  user: {\n    type: Object as PropType<ApiUserManagement.User>,\n    default: () => ({address: {}})\n  }\n})\n\nonBeforeMount(() => {\n  if (!props.user?.address) {\n    props.user.address = {}\n  }\n})\n\n</script>\n"
  },
  {
    "path": "src/views/users/EditUserPage.vue",
    "content": "<template>\n  <div class=\"flex-grow-1\" v-if=\"user\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <div class=\"text-h4\">Edit User {{ user.name && `- ${user.name}` }}</div>\n        <breadcrumb :root=\"'apps'\"/>\n      </div>\n      <v-spacer></v-spacer>\n      <v-btn variant=\"plain\" icon @click=\"getData\">\n        <v-icon>mdi-refresh</v-icon>\n      </v-btn>\n    </div>\n\n    <div\n      v-if=\"user.role === 'admin'\"\n      class=\"d-flex align-center font-weight-bold text-primary my-2\"\n    >\n      <v-icon size=\"x-small\" color=\"primary\">mdi-security</v-icon>\n      <span class=\"ma-1\">Administrator</span>\n    </div>\n\n    <div class=\"mb-4\">\n      <div class=\"d-flex\">\n        <span class=\"font-weight-bold\">Email</span>\n        <span class=\"mx-1\">\n          <copy-label :text=\"user.email\"/>\n        </span>\n      </div>\n      <div class=\"d-flex\">\n        <span class=\"font-weight-bold\">ID</span>\n        <span class=\"mx-1\">\n          <copy-label :text=\"user.id + ''\"/>\n        </span>\n      </div>\n    </div>\n\n\n    <v-tabs v-model=\"tab\" :show-arrows=\"false\" background-color=\"transparent\" color=\"primary\">\n      <v-tab value=\"tabs-account\">Account</v-tab>\n      <v-tab value=\"tabs-information\">Information</v-tab>\n    </v-tabs>\n    <v-window v-model=\"tab\">\n      <v-window-item value=\"tabs-account\">\n        <account-tab :user=\"user\"></account-tab>\n      </v-window-item>\n      <v-window-item value=\"tabs-information\">\n        <information-tab :user=\"user\"></information-tab>\n      </v-window-item>\n    </v-window>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport AccountTab from './EditUser/AccountTab.vue'\nimport InformationTab from './EditUser/InformationTab.vue'\nimport {ref} from \"vue\";\nimport {fetchUser} from '@/service'\nimport {useRouter} from 'vue-router'\nimport {useLoadingProgressLine} from \"@/components/provider\";\n\nlet user = ref<UserManagement.User | null>(null)\n\nconst tab = ref()\nconst {currentRoute} = useRouter()\nconst userId = currentRoute.value.params['id'] as string\n\nconst {show, hide} = useLoadingProgressLine()\n\nasync function getData() {\n  show()\n  const {data} = await fetchUser(userId)\n  hide()\n  if (data) {\n    user.value = data\n  }\n}\n\ngetData()\n\n</script>\n"
  },
  {
    "path": "src/views/users/UsersPage.vue",
    "content": "<template>\n  <div class=\"d-flex flex-column flex-grow-1\">\n    <div class=\"d-flex align-center py-3\">\n      <div>\n        <div class=\"text-h4\">Users</div>\n        <breadcrumb :root=\"'apps'\"/>\n      </div>\n      <v-spacer></v-spacer>\n      <v-btn class=\"text-capitalize\" color=\"primary\">\n        Create User\n      </v-btn>\n    </div>\n\n    <v-card>\n      <!-- user list -->\n      <v-row dense class=\"pa-2 align-center\">\n        <v-col cols=\"6\">\n          <v-menu offset-y left>\n            <template v-slot:activator=\"{ props}\">\n              <v-slide-x-reverse-transition mode=\"out-in\">\n                <v-btn v-show=\"selectedUsers.length > 0\" v-bind=\"props\">\n                  Actions\n                  <v-icon right>mdi-menu-down</v-icon>\n                </v-btn>\n              </v-slide-x-reverse-transition>\n            </template>\n            <v-list dense>\n              <v-list-item>\n                <v-list-item-title>Verify</v-list-item-title>\n              </v-list-item>\n              <v-list-item>\n                <v-list-item-title>Disable</v-list-item-title>\n              </v-list-item>\n              <v-divider></v-divider>\n              <v-list-item>\n                <v-list-item-title>Delete</v-list-item-title>\n              </v-list-item>\n            </v-list>\n          </v-menu>\n\n        </v-col>\n        <v-col cols=\"6\" class=\"d-flex text-right align-center\">\n          <v-text-field\n            v-model=\"searchQuery\"\n            append-inner-icon=\"mdi-magnify\"\n            class=\"flex-grow-1 mr-md-2\"\n            variant=\"solo\"\n            density=\"comfortable\"\n            hide-details\n            clearable\n            placeholder=\"e.g. filter for id, email, name, etc\"\n          ></v-text-field>\n          <v-btn\n            :loading=\"loading\"\n            @click=\"getTableData()\"\n            icon\n            flat\n            small\n            class=\"ml-2\"\n          >\n            <v-icon>mdi-refresh</v-icon>\n          </v-btn>\n        </v-col>\n      </v-row>\n\n      <v-data-table-server\n        v-model=\"selectedUsers\"\n        show-select\n        :headers=\"headers\"\n        :items=\"users\"\n        :search=\"searchQuery\"\n        :loading=\"loading\"\n        :items-length=\"total\"\n        :items-per-page=\"pageSize\"\n        @update:options=\"options = $event\"\n        class=\"flex-grow-1\"\n      >\n        <template v-slot:item.id=\"{ item  : {raw} }\">\n          <div class=\"font-weight-bold\">#\n            <copy-label :text=\"raw.id + ''\"/>\n          </div>\n        </template>\n\n        <template v-slot:item.email=\"{ item:{raw} }\">\n          <div class=\"d-flex align-center py-1\">\n            <v-avatar size=\"32\" class=\"elevation-1 grey lighten-3\">\n              <svg-icon :name=\"raw.avatar\"></svg-icon>\n            </v-avatar>\n            <div class=\"ml-1 text-caption font-weight-bold\">\n              <copy-label :text=\"raw.email\"/>\n            </div>\n          </div>\n        </template>\n\n        <template v-slot:item.verified=\"{item:{raw}}\">\n          <v-icon v-if=\"raw.verified\" small color=\"success\">\n            mdi-check-circle\n          </v-icon>\n          <v-icon v-else small>\n            mdi-circle-outline\n          </v-icon>\n        </template>\n\n        <template v-slot:item.userStatus=\"{item:{raw}}\">\n          {{ userStatusLabels [(raw as UserManagement.User).userStatus] }}\n        </template>\n\n        <template v-slot:item.role=\"{ item  : {raw} }\">\n          <v-chip\n            label\n            size=\"small\"\n            class=\"font-weight-bold\"\n            :color=\"raw.role === 'admin' ? 'primary' : undefined\"\n          >{{ raw.role }}\n          </v-chip>\n        </template>\n\n        <template v-slot:item.created=\"{ item  : {raw} }\">\n          <div>{{ raw.created }}</div>\n        </template>\n\n        <template v-slot:item.lastSignIn=\"{ item  : {raw} }\">\n          <div>{{ raw.lastSignIn }}</div>\n        </template>\n\n        <template v-slot:item.action=\"{item:{raw} }\">\n          <div class=\"actions\">\n            <v-btn flat icon :to=\"`/apps/manager-user/edit/${raw.id}`\">\n              <v-icon>mdi-open-in-new</v-icon>\n            </v-btn>\n          </div>\n        </template>\n      </v-data-table-server>\n    </v-card>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {Ref, ref} from \"vue\";\nimport {useLoading} from '@/hooks';\nimport {fetchUserList} from \"@/service\";\nimport {userStatusLabels} from '@/constants'\n\nconst {loading, startLoading, endLoading} = useLoading(true);\n\nconst options = ref()\nconst total = ref(0)\nconst pageSize = ref(10)\nconst users = ref<Array<UserManagement.User>>([])\nconst searchQuery = ref('')\nconst selectedUsers = ref<Array<UserManagement.User>>([])\nconst headers: Ref<DataTableHeader> = ref<DataTableHeader>([\n  {title: 'Id', align: 'start', key: 'id'},\n  {title: 'Email', key: 'email'},\n  {title: 'Verified', key: 'verified'},\n  {title: 'Name', align: 'start', key: 'name'},\n  {title: 'Role', key: 'role'},\n  {title: 'Created', key: 'created'},\n  {title: 'Last SignIn', key: 'lastSignIn'},\n  {title: 'Status', key: 'userStatus'},\n  {title: '', sortable: false, align: 'end', key: 'action'}\n])\n\n\nasync function getTableData() {\n  startLoading();\n  const {data} = await fetchUserList();\n  endLoading();\n  if (data) {\n    total.value = data.total\n    users.value = data.list\n  }\n}\n\nasync function init() {\n  await getTableData()\n  watch(options, (n, o) => {\n    getTableData()\n  }, {deep: true})\n}\n\ninit()\n\n</script>\n\n"
  },
  {
    "path": "src/views/users/content/user.ts",
    "content": "const user: Array<UserManagement.User> = [{\n  gender: '0',\n  birthDay: '1996-12-16',\n  'address': {},\n  phone: '123',\n  'id': \"1\",\n  'email': 'bfitchew0@ezinearticles.com',\n  'name': 'Bartel Fitchew',\n  'verified': false,\n  'created': '2019-08-09T03:14:12Z',\n  'lastSignIn': '2019-08-14T20:00:53Z',\n  userStatus: \"1\",\n  'role': 'user',\n  'avatar': 'avatar3'\n}, {\n  gender: '0',\n  'address': {},\n  birthDay: '1997-08-01',\n  phone: '123',\n  'id': \"2\",\n  'email': 'bfitchew0@ezinearticles.com',\n  'name': 'Bartel Fuck',\n  'verified': true,\n  'created': '2019-08-09T03:14:12Z',\n  'lastSignIn': '2019-08-14T20:00:53Z',\n  userStatus: \"4\",\n  'role': 'user',\n  'avatar': 'avatar2'\n}]\nexport default user\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"module\": \"ESNext\",\n    \"target\": \"ESNext\",\n    \"lib\": [\"DOM\", \"ESNext\"],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"jsx\": \"preserve\",\n    \"jsxFactory\": \"h\",\n    \"jsxFragmentFactory\": \"Fragment\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"noUnusedLocals\": true,\n    \"strictNullChecks\": true,\n    \"allowJs\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"paths\": {\n      \"~/*\": [\"./*\"],\n      \"@/*\": [\"./src/*\"]\n    },\n    \"types\": [\"vite/client\", \"node\", \"unplugin-icons/types/vue\" ,\"vuetify\"]\n  },\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import {setupVitePlugins, getSrcPath, getRootPath} from './build';\n\n// Utilities\nimport {defineConfig, loadEnv} from 'vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig(configEnv => {\n  const viteEnv = loadEnv(configEnv.mode, process.cwd()) as unknown as ImportMetaEnv;\n  const rootPath = getRootPath();\n  const srcPath = getSrcPath();\n\n  return {\n    plugins: setupVitePlugins(viteEnv),\n    define: {'process.env': {}},\n    resolve: {\n      alias: {\n        '~': rootPath,\n        '@': srcPath,\n      },\n      extensions: [\n        '.js',\n        '.json',\n        '.jsx',\n        '.mjs',\n        '.ts',\n        '.tsx',\n        '.vue',\n      ],\n    },\n    server: {\n      port: 3322,\n      open: true,\n      host: '0.0.0.0',\n    },\n    build: {\n      reportCompressedSize: false,\n      sourcemap: false,\n      commonjsOptions: {\n        ignoreTryCatch: false\n      }\n    },\n    assetsInclude:[\"**/*.bpmn\"]\n  }\n})\n"
  }
]