Browse Source

refactor:重构

hongk 1 week ago
parent
commit
10e47e0a2a
100 changed files with 6467 additions and 0 deletions
  1. 81 0
      common/eslint.config.mjs
  2. 19 0
      common/prettier.config.cjs
  3. 101 0
      common/src/App.vue
  4. 46 0
      common/src/api/index.ts
  5. 25 0
      common/src/api/login/index.ts
  6. 22 0
      common/src/api/login/types.ts
  7. 39 0
      common/src/api/messageApi.ts
  8. 21 0
      common/src/api/system-config/department/index.ts
  9. 32 0
      common/src/api/system-config/department/types.ts
  10. 81 0
      common/src/api/system-config/field/index.ts
  11. 44 0
      common/src/api/system-config/field/types.ts
  12. 12 0
      common/src/api/system-config/log.ts
  13. 39 0
      common/src/api/system-config/lowcode.ts
  14. 29 0
      common/src/api/system-config/role/index.ts
  15. 25 0
      common/src/api/system-config/user/index.ts
  16. 0 0
      common/src/api/system-config/user/types.ts
  17. 11 0
      common/src/api/types/custom.ts
  18. 48 0
      common/src/api/types/login.ts
  19. BIN
      common/src/assets/imgs/add.png
  20. BIN
      common/src/assets/imgs/avatar.png
  21. BIN
      common/src/assets/imgs/del.png
  22. BIN
      common/src/assets/imgs/location.png
  23. BIN
      common/src/assets/imgs/logo.png
  24. BIN
      common/src/assets/imgs/more.png
  25. 211 0
      common/src/assets/svgs/403.svg
  26. 197 0
      common/src/assets/svgs/404.svg
  27. 325 0
      common/src/assets/svgs/500.svg
  28. 13 0
      common/src/assets/svgs/del.svg
  29. 7 0
      common/src/assets/svgs/guize.svg
  30. 3 0
      common/src/assets/svgs/icon.svg
  31. 188 0
      common/src/assets/svgs/login-box-bg.svg
  32. 3 0
      common/src/assets/svgs/message.svg
  33. 12 0
      common/src/assets/svgs/warning.svg
  34. 84 0
      common/src/axios/config.ts
  35. 42 0
      common/src/axios/index.ts
  36. 73 0
      common/src/axios/service.ts
  37. 31 0
      common/src/axios/types/index.ts
  38. 46 0
      common/src/business-components/BackButton.vue
  39. 52 0
      common/src/business-components/ButtonBottom.vue
  40. 34 0
      common/src/business-components/DeptSelect.vue
  41. 44 0
      common/src/business-components/DistrictSelect.vue
  42. 3 0
      common/src/business-components/DynamicForm/index.ts
  43. 146 0
      common/src/business-components/DynamicForm/src/DynamicFormDemo.vue
  44. 15 0
      common/src/business-components/DynamicForm/src/api/dynamic-form.ts
  45. 42 0
      common/src/business-components/DynamicForm/src/components/Autocomplete.vue
  46. 54 0
      common/src/business-components/DynamicForm/src/components/Cascader.vue
  47. 39 0
      common/src/business-components/DynamicForm/src/components/Checkbox.vue
  48. 163 0
      common/src/business-components/DynamicForm/src/components/CheckboxOther.vue
  49. 340 0
      common/src/business-components/DynamicForm/src/components/ChildForm.vue
  50. 64 0
      common/src/business-components/DynamicForm/src/components/Date.vue
  51. 94 0
      common/src/business-components/DynamicForm/src/components/District.vue
  52. 108 0
      common/src/business-components/DynamicForm/src/components/FileUpload/FileUpload.vue
  53. 135 0
      common/src/business-components/DynamicForm/src/components/FileUpload/FileView.vue
  54. BIN
      common/src/business-components/DynamicForm/src/components/FileUpload/file.png
  55. 58 0
      common/src/business-components/DynamicForm/src/components/FileUpload/utils.js
  56. 17 0
      common/src/business-components/DynamicForm/src/components/FormHead1.vue
  57. 22 0
      common/src/business-components/DynamicForm/src/components/FormHead2.vue
  58. 46 0
      common/src/business-components/DynamicForm/src/components/Input.vue
  59. 37 0
      common/src/business-components/DynamicForm/src/components/InputNumber.vue
  60. 361 0
      common/src/business-components/DynamicForm/src/components/Map.vue
  61. 35 0
      common/src/business-components/DynamicForm/src/components/Radio.vue
  62. 112 0
      common/src/business-components/DynamicForm/src/components/Select.vue
  63. 72 0
      common/src/business-components/DynamicForm/src/hooks/useForm.ts
  64. 131 0
      common/src/business-components/DynamicForm/src/hooks/useOptions.ts
  65. 141 0
      common/src/business-components/DynamicForm/src/style/index.less
  66. 43 0
      common/src/business-components/DynamicForm/src/templates/Anchor.vue
  67. 52 0
      common/src/business-components/DynamicForm/src/templates/FormCommon.vue
  68. 53 0
      common/src/business-components/DynamicForm/src/templates/FormDeptAuth.vue
  69. 346 0
      common/src/business-components/DynamicForm/src/templates/FormMixins.vue
  70. 33 0
      common/src/business-components/DynamicForm/src/types/index.ts
  71. 33 0
      common/src/business-components/HeaderFlag/Flag.vue
  72. BIN
      common/src/business-components/HeaderFlag/imgs/linye-icon.png
  73. BIN
      common/src/business-components/HeaderFlag/imgs/linye-img.png
  74. BIN
      common/src/business-components/HeaderFlag/imgs/nongye-icon.png
  75. BIN
      common/src/business-components/HeaderFlag/imgs/nongye-img.png
  76. 59 0
      common/src/business-components/ImportFile.vue
  77. 202 0
      common/src/business-components/Notice.vue
  78. 93 0
      common/src/business-components/SelectDept.vue
  79. 373 0
      common/src/business-components/SelectUser/SelectUser.vue
  80. 5 0
      common/src/business-components/SelectUser/index.ts
  81. 5 0
      common/src/business-components/SelectUser/types.ts
  82. 320 0
      common/src/business-components/Users.vue
  83. 3 0
      common/src/components/Backtop/index.ts
  84. 22 0
      common/src/components/Backtop/src/Backtop.vue
  85. 3 0
      common/src/components/Breadcrumb/index.ts
  86. 127 0
      common/src/components/Breadcrumb/src/Breadcrumb.vue
  87. 30 0
      common/src/components/Breadcrumb/src/helper.ts
  88. 3 0
      common/src/components/Button/index.ts
  89. 121 0
      common/src/components/Button/src/Button.vue
  90. 3 0
      common/src/components/Collapse/index.ts
  91. 34 0
      common/src/components/Collapse/src/Collapse.vue
  92. 5 0
      common/src/components/ConfigGlobal/index.ts
  93. 62 0
      common/src/components/ConfigGlobal/src/ConfigGlobal.vue
  94. 5 0
      common/src/components/ConfigGlobal/src/types/index.ts
  95. 3 0
      common/src/components/ContentWrap/index.ts
  96. 56 0
      common/src/components/ContentWrap/src/ContentWrap.vue
  97. 12 0
      common/src/components/ContextMenu/index.ts
  98. 74 0
      common/src/components/ContextMenu/src/ContextMenu.vue
  99. 7 0
      common/src/components/ContextMenu/src/types/index.ts
  100. 5 0
      common/src/components/Descriptions/index.ts

+ 81 - 0
common/eslint.config.mjs

@@ -0,0 +1,81 @@
+// 引入vue模版的eslint
+import pluginVue from 'eslint-plugin-vue'
+import eslint from '@eslint/js'
+// ts-eslint解析器,使 eslint 可以解析 ts 语法
+import tseslint from 'typescript-eslint'
+// vue文件解析器
+import vueParser from 'vue-eslint-parser'
+import prettier from 'eslint-plugin-prettier'
+
+export default tseslint.config({
+    // ignores: ['node_modules', 'prettier.config.cjs', 'dist*'],
+    files: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
+    // tseslint.config添加了extends扁平函数,直接用。否则是eslint9.0版本是没有extends的
+    extends: [
+        eslint.configs.recommended,
+        ...tseslint.configs.recommended,
+        ...pluginVue.configs['flat/essential']
+    ],
+    plugins: {
+        prettier
+    },
+    languageOptions: {
+        parser: vueParser, // 使用vue解析器,这个可以识别vue文件
+        parserOptions: {
+            parser: tseslint.parser, // 在vue文件上使用ts解析器
+            sourceType: 'module',
+            ecmaVersion: 2020,
+            ecmaFeatures: {
+                jsx: true
+            }
+        }
+    },
+    rules: {
+        'prettier/prettier': 'error',
+        'no-useless-escape': 0,
+        'no-undef': 0,
+        'vue/no-setup-props-destructure': 0,
+        'vue/script-setup-uses-vars': 1,
+        'vue/no-reserved-component-names': 0,
+        '@typescript-eslint/ban-ts-ignore': 0,
+        '@typescript-eslint/explicit-function-return-type': 0,
+        '@typescript-eslint/no-explicit-any': 0,
+        '@typescript-eslint/no-var-requires': 0,
+        '@typescript-eslint/no-empty-function': 0,
+        'vue/custom-event-name-casing': 0,
+        'no-use-before-define': 0,
+        '@typescript-eslint/no-use-before-define': 0,
+        '@typescript-eslint/ban-ts-comment': 0,
+        '@typescript-eslint/ban-types': 0,
+        '@typescript-eslint/no-non-null-assertion': 0,
+        '@typescript-eslint/explicit-module-boundary-types': 0,
+        '@typescript-eslint/no-unused-vars': 0,
+        'no-unused-vars': 0,
+        'space-before-function-paren': 0,
+
+        'vue/attributes-order': 0,
+        'vue/one-component-per-file': 0,
+        'vue/html-closing-bracket-newline': 0,
+        'vue/max-attributes-per-line': 0,
+        'vue/multiline-html-element-content-newline': 0,
+        'vue/singleline-html-element-content-newline': 0,
+        'vue/attribute-hyphenation': 0,
+        'vue/require-default-prop': 0,
+        'vue/require-explicit-emits': 0,
+        'vue/html-self-closing': [
+            1,
+            {
+                html: {
+                    void: 'always',
+                    normal: 'never',
+                    component: 'always'
+                },
+                svg: 'always',
+                math: 'always'
+            }
+        ],
+        'vue/multi-word-component-names': 0,
+        'vue/no-v-html': 0,
+        'vue/require-toggle-inside-transition': 0
+    }
+})

+ 19 - 0
common/prettier.config.cjs

@@ -0,0 +1,19 @@
+module.exports = {
+    printWidth: 100,
+    tabWidth: 2,
+    useTabs: false,
+    semi: false,
+    vueIndentScriptAndStyle: false,
+    singleQuote: true,
+    quoteProps: 'as-needed',
+    bracketSpacing: true,
+    trailingComma: 'none',
+    jsxSingleQuote: false,
+    arrowParens: 'always',
+    insertPragma: false,
+    requirePragma: false,
+    proseWrap: 'never',
+    htmlWhitespaceSensitivity: 'strict',
+    endOfLine: 'auto',
+    rangeStart: 0
+}

+ 101 - 0
common/src/App.vue

@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import { computed, watch } from 'vue'
+import { useAppStore } from './store/modules/app'
+import { ConfigGlobal } from './components/ConfigGlobal'
+import { useDesign } from './hooks/web/useDesign'
+import { getCodeList, getDeptTree, getDistrictTree } from './api'
+import { useUserStore } from './store/modules/user'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('app')
+
+const appStore = useAppStore()
+
+const currentSize = computed(() => appStore.getCurrentSize)
+
+const greyMode = computed(() => appStore.getGreyMode)
+
+const userStore = useUserStore()
+
+appStore.initTheme()
+
+// 全局存一下字典数据
+const getGlobalCodeList = async () => {
+  const {
+    success,
+    data: { list }
+  } = await getCodeList({
+    current: 1,
+    size: 10000000
+  })
+  if (success) {
+    const formatter = list.map((i) => {
+      return {
+        codeName: i.codeName,
+        codeType: i.codeType,
+        codeValue: i.codeValue
+      }
+    })
+    sessionStorage.setItem('CODE_ENUM', JSON.stringify(formatter))
+  }
+}
+// 存一下公共部门树
+const getDeptTreeFunc = async () => {
+  const { data, success } = await getDeptTree()
+  if (success) {
+    sessionStorage.setItem('DEPT_TREE', JSON.stringify(data))
+  }
+}
+// 存一下行政区划
+const getDistrictFunc = async () => {
+  const { data, success } = await getDistrictTree()
+  if (success) {
+    sessionStorage.setItem('DISTRICT_TREE', JSON.stringify(data))
+  }
+}
+// watch(
+//   () => userStore.getToken,
+//   (val) => {
+//     if (val) {
+//       getGlobalCodeList()
+//       getDeptTreeFunc()
+//       getDistrictFunc()
+//     }
+//   },
+//   {
+//     immediate: true
+//   }
+// )
+</script>
+
+<template>
+  <ConfigGlobal :size="currentSize">
+    <RouterView :class="greyMode ? `${prefixCls}-grey-mode` : ''" />
+  </ConfigGlobal>
+</template>
+
+<style lang="less">
+@prefix-cls: ~'@{adminNamespace}-app';
+
+.size {
+  width: 100%;
+  height: 100%;
+}
+
+html,
+body {
+  padding: 0 !important;
+  margin: 0 !important;
+  overflow: hidden;
+  .size;
+
+  #app {
+    .size;
+  }
+}
+
+.@{prefix-cls}-grey-mode {
+  filter: grayscale(100%);
+}
+</style>

+ 46 - 0
common/src/api/index.ts

@@ -0,0 +1,46 @@
+/*
+ * @Date: 2024-09-29 16:21:01
+ * @LastEditTime: 2024-11-18 14:24:09
+ * @FilePath: /web/common/src/api/index.ts
+ * @Description: common api 部分
+ */
+
+import request from '../axios'
+
+// 埋点记录进入路由
+export const saveCommonRoute = (data) => {
+  return request.post({ url: '/api/oauthConfig/applicationCommonFunction/access', data })
+}
+// 查询字典列表
+export const getCodeList = (data) => {
+  return request.post({ url: '/api/sys/code/query', data })
+}
+
+/**
+ * 获取部门树
+ * @param parentId 上级部门ID
+ * @param deptName    部门名称
+ */
+export const getDeptTree = (params = {}) => {
+  return request.get({ url: '/api/sys/dept/getDeptTree', params })
+}
+// 获取行政区划树
+export const getDistrictTree = (id: number = 0) => {
+  return request.get({
+    url: `/api/sys/district/getDistrictTree?parentId=${id}`
+  })
+}
+/**
+ * 获取系统用户列表
+ * @param deptId 部门ID
+ * @param userName    部门名称
+ * @param deptIdList 部门ID集合
+ */
+export const getUserList = (data) => {
+  return request.post({ url: '/api/sys/user/query', data })
+}
+
+// 修改密码
+export const changePassword = (data) => {
+  return request.post({ url: '/api/sys/token/changePassword', data })
+}

+ 25 - 0
common/src/api/login/index.ts

@@ -0,0 +1,25 @@
+import request from '../../axios'
+
+export const loginOutApi = () => {
+  return request.get({ url: '/api/sys/token/logout' })
+}
+
+export const myLogin = (data) => {
+  return request.post({ url: '/api/sys/token/login', data })
+}
+
+export const getMenuList = (params) => {
+  return request.get({ url: '/api/sys/token/getUserMenuTree', params })
+}
+
+export const codeLogin = (params) => {
+  return request.get({ url: '/api/oauth/loginByCode', params })
+}
+
+export const getAuthBtn = (params) => {
+  return request.get({ url: '/api/sys/token/getPermissionList', params })
+}
+
+export const codeLogout = (params) => {
+  return request.get({ url: '/api/oauth/logout', params })
+}

+ 22 - 0
common/src/api/login/types.ts

@@ -0,0 +1,22 @@
+export interface UserLoginType {
+  username: string
+  password: string
+}
+
+export interface UserType {
+  username: string
+  userNo: string
+  password: string
+  role?: string
+  roleId?: string
+  loginType?: number
+  phone?: string
+  deptList?: any[]
+}
+
+export interface myUserType {
+  username: string
+  password: string
+  loginType: number
+  phone: string
+}

+ 39 - 0
common/src/api/messageApi.ts

@@ -0,0 +1,39 @@
+// 需要在调完接口之后 显示message的接口集合
+
+// 提交成功信息提示 接口路径白名单
+// 去除环境变量VITE_API_BASE_PATH并且不包含参数的部分
+export const API_URL_MESSAGE_TIP_COMMON = [
+  '/api/sys/dept/create',
+  '/api/sys/dept/update',
+  '/api/sys/role/create',
+  '/api/sys/role/update',
+  '/api/sys/role/allocateFunctions',
+  '/api/sys/role/allocateUsers',
+  '/api/sys/role/deleteById',
+  '/api/sys/role/allocateUserGroups',
+  '/api/sys/user/create',
+  '/api/sys/user/update',
+  '/api/sys/user/delete',
+  '/api/sys/user/deleteByIds',
+  '/api/sys/user/allocateRoles',
+  '/api/sys/user/resetPassword',
+  '/api/oauthConfig/applicationRole/create',
+  '/api/oauthConfig/applicationRole/update',
+  '/api/oauthConfig/applicationRole/deleteById',
+  '/api/oauthConfig/applicationRole/allocateFunctions',
+  '/api/oauthConfig/applicationRole/allocateUsers',
+  '/api/sys/loginLog/deleteById',
+  '/api/sys/errorLog/deleteById',
+  '/api/sys/bizLog/deleteById',
+  '/api/dynamicform/dynamicServiceTableClassify/create',
+  '/api/dynamicform/dynamicServiceTableClassify/update',
+  '/api/dynamicform/dynamicServiceTable/create',
+  '/api/dynamicform/dynamicServiceTable/update',
+  '/api/dynamicform/dynamicServiceTable/deleteById',
+  '/api/dynamicform/dynamicServiceTable/updateFieldConfig',
+  '/api/camunda/model/update',
+  '/api/camunda/model/create',
+  '/api/camunda/model/deleteById',
+  '/api/camunda/model/deleteByIds',
+  '/api/camunda/model/deployment'
+]

+ 21 - 0
common/src/api/system-config/department/index.ts

@@ -0,0 +1,21 @@
+import request from '../../../axios'
+
+export const addDept = (data) => {
+    return request.post({url: '/api/sys/dept/create', data})
+}
+
+export const getDeptTree = (params) => {
+    return request.get({url: '/api/sys/dept/getDeptTree', params})
+}
+
+export const queryDept = (params) => {
+    return request.get({url: '/api/sys/dept/findById', params})
+}
+
+export const updateDept = (data) => {
+    return request.post({url: '/api/sys/dept/update', data})
+}
+
+export const delDeptTree = (params) => {
+    return request.get({url: '/api/sys/dept/deleteById', params})
+}

+ 32 - 0
common/src/api/system-config/department/types.ts

@@ -0,0 +1,32 @@
+export interface DepartmentItem {
+    id: string
+    departmentName?: string
+    children?: DepartmentItem[]
+}
+
+export interface DepartmentListResponse {
+    list: DepartmentItem[]
+}
+
+export interface DepartmentUserParams {
+    pageSize: number
+    pageIndex: number
+    id: string
+    username?: string
+    account?: string
+}
+
+export interface DepartmentUserItem {
+    id: string
+    username: string
+    account: string
+    email: string
+    createTime: string
+    role: string
+    department: DepartmentItem
+}
+
+export interface DepartmentUserResponse {
+    list: DepartmentUserItem[]
+    total: number
+}

+ 81 - 0
common/src/api/system-config/field/index.ts

@@ -0,0 +1,81 @@
+import request from '../../../axios'
+import { IBusiness } from './types'
+
+export const baseDataList = () => {
+  return request.get({ url: '/api/dynamicform/dynamicDataTable/findList' })
+}
+
+// 动态表单 - 业务表分类
+export const tree = () => {
+  return request.post({
+    url: `/api/dynamicform/dynamicServiceTableClassify/findList`
+  })
+}
+export const createBusinessType = (data) => {
+  return request.post({ url: '/api/dynamicform/dynamicServiceTableClassify/create', data })
+}
+export const updateBusinessType = (data) => {
+  return request.post({ url: '/api/dynamicform/dynamicServiceTableClassify/update', data })
+}
+export const findBusinessType = (leadServiceClassifyId: number) => {
+  return request.get({
+    url: `/api/dynamicform/dynamicServiceTableClassify/findById?leadServiceClassifyId=${leadServiceClassifyId}`
+  })
+}
+export const delBusinessType = (data) => {
+  return request.post({
+    url: `/api/dynamicform/dynamicServiceTableClassify/deleteById`,
+    data
+  })
+}
+// 业务表
+export const BusinessTableList = (data) => {
+  return request.post({
+    url: `/api/dynamicform/dynamicServiceTable/findListByPage`,
+    data
+  })
+}
+export const createBusinessTable = (data: IBusiness) => {
+  return request.post({ url: '/api/dynamicform/dynamicServiceTable/create', data })
+}
+export const updateBusinessTable = (data: IBusiness) => {
+  return request.post({ url: '/api/dynamicform/dynamicServiceTable/update', data })
+}
+export const findBusinessTable = (id: number | null) => {
+  return request.get({
+    url: `/api/dynamicform/dynamicServiceTable/findById?leadServiceTableId=${id}`
+  })
+}
+export const delBusinessTable = (id: number) => {
+  return request.post({
+    url: '/api/dynamicform/dynamicServiceTable/deleteById',
+    data: {
+      id
+    }
+  })
+}
+// 字段配置
+export const findFieldConfigById = (leadServiceTableId: number) => {
+  return request.get({
+    url: `/api/dynamicform/dynamicServiceTable/findFieldConfigById?leadServiceTableId=${leadServiceTableId}`
+  })
+}
+export const updateFieldConfig = (data) => {
+  return request.post({
+    url: `/api/dynamicform/dynamicServiceTable/updateFieldConfig`,
+    data
+  })
+}
+// 查询业务字典接口
+export const queryTypeList = () => {
+  return request.get({
+    url: `/api/sys/code/queryTypeList`
+  })
+}
+
+// 查询字段
+export const findFormById = (leadServiceTableId: number) => {
+  return request.get({
+    url: `/api/dynamicform/dynamicServiceTable/findFormById?leadServiceTableId=${leadServiceTableId}`
+  })
+}

+ 44 - 0
common/src/api/system-config/field/types.ts

@@ -0,0 +1,44 @@
+// 列表行数据
+export interface IFieldRow {
+    id: number
+    tableDesc: string
+    tableName: string
+    tableRemark: string
+    version?: number
+}
+
+// 新增字段的类型
+export interface IField {
+    fieldName: string
+    fieldDesc: string
+    fieldLength: string
+    fieldType: number
+}
+
+// 分页查询参数
+export interface IFieldPage {
+    current: number
+    size: number
+    keyWord?: string
+}
+
+// 业务表
+export interface IBusiness {
+    id?: 0
+    serviceNo: string
+    serviceName: string
+    orderNo?: number
+    classifyId?: number
+    tableName?: string
+    pageTemplate?: string
+    pageType: number
+    isEnabled: number
+}
+
+// 业务分类
+export interface IBusinessType {
+    id?: number
+    taskCode: string
+    taskId: number
+    taskName: string
+}

+ 12 - 0
common/src/api/system-config/log.ts

@@ -0,0 +1,12 @@
+import request from '../../axios'
+
+export const getWorkList = (data) => {
+  return request.post({ url: '/api/sys/bizLog/query', data })
+}
+
+export const delWorkLog = (params) => {
+  return request.get({ url: '/api/sys/bizLog/deleteById', params })
+}
+export const exportWorkList = (params) => {
+  return request.post({ url: '/api/sys/bizLog/export', params, responseType: 'blob' })
+}

+ 39 - 0
common/src/api/system-config/lowcode.ts

@@ -0,0 +1,39 @@
+import request from '../../axios'
+
+export interface IFlowPage {
+    current: number
+    size: number
+}
+
+export const list = (data: IFlowPage) => {
+    return request.post({url: '/api/camunda/model/query', data})
+}
+
+export interface IFlowRow {
+    id?: number
+    jsonXml?: string
+    deployId?: string
+    deployIds?: string[]
+    ids?: number[]
+    version?: number
+}
+
+export const update = (data: IFlowRow) => {
+    return request.post({url: '/api/camunda/model/update', data})
+}
+
+export const create = (data: IFlowRow) => {
+    return request.post({url: '/api/camunda/model/create', data})
+}
+export const del = (id: number) => {
+    return request.get({url: `/api/camunda/model/deleteById?id=${id}`})
+}
+export const deleteMult = (data: IFlowRow) => {
+    return request.post({url: '/api/camunda/model/deleteByIds', data})
+}
+export const deployment = (id: number) => {
+    return request.get({url: `/api/camunda/model/deployment?id=${id}`})
+}
+export const find = (id: number) => {
+    return request.get({url: `/api/camunda/model/findById?id=${id}`})
+}

+ 29 - 0
common/src/api/system-config/role/index.ts

@@ -0,0 +1,29 @@
+import request from '../../../axios'
+
+export const getRoleList = (data) => {
+    return request.post({url: '/api/oauthConfig/applicationRole/query', data})
+}
+export const addRole = (data) => {
+    return request.post({url: '/api/oauthConfig/applicationRole/create', data})
+}
+export const updateRole = (data) => {
+    return request.post({url: '/api/oauthConfig/applicationRole/update', data})
+}
+export const authRole = (data) => {
+    return request.post({url: '/api/oauthConfig/applicationRole/allocateFunctions', data})
+}
+export const getAuthTree = (params) => {
+    return request.get({url: '/api/oauthConfig/applicationRole/findAllocatedFunctions', params})
+}
+export const getAuthUsers = (params) => {
+    return request.get({url: '/api/oauthConfig/applicationRole/findAllocatedUsers', params})
+}
+export const authUsers = (data) => {
+    return request.post({url: '/api/oauthConfig/applicationRole/allocateUsers', data})
+}
+export const delAuthUsers = (params) => {
+    return request.get({url: '/api/oauthConfig/applicationRole/deleteById', params})
+}
+export const getUserList = (data) => {
+    return request.post({url: '/api/oauthConfig/application/queryUser', data})
+}

+ 25 - 0
common/src/api/system-config/user/index.ts

@@ -0,0 +1,25 @@
+import request from '../../../axios'
+
+export const addUser = (data) => {
+    return request.post({url: '/api/sys/user/create', data})
+}
+
+export const getUserList = (data) => {
+    return request.post({url: '/api/sys/user/query', data})
+}
+
+export const queryUser = (params) => {
+    return request.get({url: '/api/sys/user/findById', params})
+}
+
+export const updateUser = (data) => {
+    return request.post({url: '/api/sys/user/update', data})
+}
+
+export const delUser = (params) => {
+    return request.get({url: '/api/sys/user/delete', params})
+}
+
+export const delUsers = (data) => {
+    return request.post({url: '/api/sys/user/deleteByIds', data})
+}

+ 0 - 0
common/src/api/system-config/user/types.ts


+ 11 - 0
common/src/api/types/custom.ts

@@ -0,0 +1,11 @@
+export interface DepartmentItem {
+    id: string
+    name: string
+    children?: DepartmentItem[]
+}
+
+// 字典列表
+export interface ICodeItem {
+    value: string
+    label: string
+}

+ 48 - 0
common/src/api/types/login.ts

@@ -0,0 +1,48 @@
+export interface UserLoginType {
+    username: string
+    password: string
+}
+
+export interface UserState {
+    userInfo?: UserType
+    tokenKey: string
+    token: string
+    roleRouters?: string[] | AppCustomRouteRecordRaw[]
+    rememberMe: boolean
+    loginInfo?: UserLoginType
+    btnAuth?: any[]
+    appInfo: any
+}
+
+export interface UserType {
+    username: string
+    userNo: string
+    password: string
+    role?: string
+    roleId?: string
+    loginType?: number
+    phone?: string
+    deptList?: any[]
+    appInfo?: appInfoType | undefined
+}
+
+export interface appInfoType {
+    appCode: string
+    appName: string
+    appId: number
+}
+
+export interface myUserType {
+    username: string
+    password: string
+    loginType: number
+    phone: string
+}
+
+export interface IDeptType {
+    deptCode: string | null
+    deptId: number
+    deptName: string
+    deptType: string
+    fullPath: string
+}

BIN
common/src/assets/imgs/add.png


BIN
common/src/assets/imgs/avatar.png


BIN
common/src/assets/imgs/del.png


BIN
common/src/assets/imgs/location.png


BIN
common/src/assets/imgs/logo.png


BIN
common/src/assets/imgs/more.png


+ 211 - 0
common/src/assets/svgs/403.svg

@@ -0,0 +1,211 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" enable-background="new 0 0 800 800">
+    <style>.st26{fill:#fff}</style>
+    <g id="图层_11">
+        <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="401.773" y1="162.104" x2="401.773"
+                        y2="717.596">
+            <stop offset="0" stop-color="#F4F2FB"/>
+            <stop offset="1" stop-color="#E1EEF5"/>
+        </linearGradient>
+        <path d="M485.03 203.46c-38.37 30.29-120.74 33.81-181.17-2.22s-172-31.38-202.22 34.87 37.19 131.33 12.78 178.98S8.66 530.13 64.45 611.49s126.6 60.62 169.22 52.45c84.17-16.13 189.79 115.67 308.62 16.13 68.47-57.35 170.44 42.09 210.17-81.36 32.78-101.86-85.67-139.5-49.97-208.03 37.96-72.88 30.67-159.24-10.46-201.06-38.31-38.96-140.75-38.46-207 13.84z"
+              style="fill:url(#SVGID_1_)"/>
+        <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="494.782" y1="599.604" x2="494.782"
+                        y2="428.659">
+            <stop offset=".34" stop-color="#B0B9E1"/>
+            <stop offset=".866" stop-color="#EAF0F8"/>
+        </linearGradient>
+        <path d="M406.65 428.66h216.44l-22.53 49.03s59.19 57.87-14.13 121.91c-134.28-44.17-221.74-37.1-219.98-38.87 1.77-1.76 40.2-132.07 40.2-132.07z"
+              style="fill:url(#SVGID_2_)"/>
+        <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="116.855" y1="542.49" x2="116.855" y2="405.316">
+            <stop offset=".227" stop-color="#B7ACE0"/>
+            <stop offset=".789" stop-color="#E8E7FA"/>
+        </linearGradient>
+        <path d="M117.64 405.56s-.22-.57-.52.04c-2.7 5.49-27.15 64.96-29.09 110.86 0 0-4.08 26.37 30.11 26.02 28.54-.29 27.78-24.6 27.68-32.79-.39-33.22-28.18-104.13-28.18-104.13z"
+              style="fill:url(#SVGID_3_)"/>
+        <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="116.857" y1="420.547" x2="116.857"
+                        y2="571.681">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset=".818" stop-color="#AFB0E7"/>
+        </linearGradient>
+        <path d="M116.86 571.68c-.55 0-1-.45-1-1V421.55c0-.55.45-1 1-1s1 .45 1 1v149.13c0 .55-.45 1-1 1z"
+              style="fill:url(#SVGID_4_)"/>
+        <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="617.984" y1="450.968" x2="617.984"
+                        y2="362.644">
+            <stop offset=".227" stop-color="#CCD4F4"/>
+            <stop offset=".789" stop-color="#ECF1FB"/>
+        </linearGradient>
+        <path d="M618.49 362.8s-.14-.37-.33.03c-1.74 3.53-17.48 41.83-18.73 71.38 0 0-2.63 16.98 19.39 16.76 18.38-.18 17.89-15.84 17.82-21.11-.25-21.4-18.15-67.06-18.15-67.06z"
+              style="fill:url(#SVGID_5_)"/>
+        <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="617.985" y1="372.451" x2="617.985"
+                        y2="469.764">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset="1" stop-color="#A6A8E2"/>
+        </linearGradient>
+        <path d="M617.99 469.76c-.36 0-.64-.29-.64-.64V373.1c0-.36.29-.64.64-.64s.64.29.64.64v96.02c0 .36-.29.64-.64.64z"
+              style="fill:url(#SVGID_6_)"/>
+        <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="463.902" y1="88.362" x2="429.148" y2="148.558">
+            <stop offset="0" stop-color="#FFDB80"/>
+            <stop offset="1" stop-color="#FFBB24"/>
+        </linearGradient>
+        <circle cx="446.52" cy="118.46" r="34.75" style="fill:url(#SVGID_7_)"/>
+        <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="421.565" y1="118.828" x2="421.565"
+                        y2="176.282">
+            <stop offset="0" stop-color="#F9FAFE"/>
+            <stop offset="1" stop-color="#E5EDF7"/>
+        </linearGradient>
+        <path d="M466.3 137.41h-34.57c-2.23-10.61-11.65-18.58-22.93-18.58s-20.69 7.97-22.93 18.58h-9.05c-10.73 0-19.44 8.7-19.44 19.44 0 10.73 8.7 19.44 19.44 19.44h89.47c10.73 0 19.44-8.7 19.44-19.44.01-10.74-8.69-19.44-19.43-19.44z"
+              style="fill:url(#SVGID_8_)"/>
+        <g>
+            <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="688.586" y1="540.208" x2="688.586"
+                            y2="512.38">
+                <stop offset=".227" stop-color="#AFB0E7"/>
+                <stop offset="1" stop-color="#ECF1FB"/>
+            </linearGradient>
+            <circle cx="688.59" cy="526.29" r="13.91" style="fill:url(#SVGID_9_)"/>
+            <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="688.635" y1="515.894" x2="688.635"
+                            y2="560.69">
+                <stop offset="0" stop-color="#DDE1F6"/>
+                <stop offset=".818" stop-color="#A6A8E2"/>
+            </linearGradient>
+            <path d="M688.64 560.69c-.24 0-.43-.19-.43-.43v-43.94c0-.24.19-.43.43-.43s.43.19.43.43v43.94a.44.44 0 01-.43.43z"
+                  style="fill:url(#SVGID_10_)"/>
+        </g>
+        <g>
+            <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="2622.045" y1="266.481" x2="2451.058"
+                            y2="562.64" gradientTransform="matrix(-1 0 0 1 2941.346 0)">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M248.82 393.99c0-24.52-.03-49.03.01-73.54.02-14.37 4.24-18.36 17.97-20.53 41.87-6.61 82.03-18.72 117.91-42.29 10.38-6.82 18.3-7.59 29.06-.47 34.85 23.06 73.26 37.11 114.55 42.8 13.12 1.81 16.84 5.88 16.85 19.25.04 45.72-.4 91.44.18 137.15.34 26.77-8.17 49.99-24.02 70.73-31.46 41.17-74.88 63.76-122.21 80.03-2.5.86-5.83.67-8.36-.23-38.47-13.74-74.58-31.84-104.15-61.09-22.97-22.73-37.84-49.56-37.79-83.22.03-22.87.01-45.73 0-68.59z"
+                  style="fill:url(#SVGID_11_)"/>
+            <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="2625.25" y1="279.944" x2="2462.749"
+                            y2="561.403" gradientTransform="matrix(-1 0 0 1 2941.346 0)">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M247.94 401.44c0-23.21-.03-46.42.01-69.63.02-13.61 4.06-17.38 17.23-19.43 40.15-6.26 78.67-17.72 113.07-40.04 9.95-6.46 17.55-7.18 27.86-.44 33.42 21.83 70.25 35.14 109.84 40.52 12.58 1.71 16.14 5.56 16.15 18.22.03 43.28-.38 86.57.18 129.84.33 25.34-7.83 47.33-23.03 66.96-30.17 38.98-71.81 60.36-117.19 75.77-2.4.81-5.59.64-8.01-.22-36.89-13.01-71.52-30.14-99.87-57.84-22.03-21.52-36.28-46.91-36.23-78.78.02-21.65-.01-43.29-.01-64.93z"
+                  style="fill:url(#SVGID_12_)"/>
+            <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="361.421" y1="346.477" x2="449.513"
+                            y2="499.057">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M411.59 435.75c23.18-5.61 40.41-26.11 40.41-50.49 0-28.68-23.85-52.01-53.17-52.01s-53.17 23.33-53.17 52.01c0 24.38 17.24 44.88 40.41 50.49v85.2h25.52v-36.38h32.67v-24.96h-32.67v-23.86zm-40.41-50.49c0-14.91 12.41-27.05 27.65-27.05s27.65 12.14 27.65 27.05-12.41 27.05-27.65 27.05-27.65-12.14-27.65-27.05z"
+                  style="fill:url(#SVGID_13_)"/>
+            <path class="st26"
+                  d="M407.67 439.03c21.8-5.39 38.01-25.1 38.01-48.54 0-27.58-22.43-50.01-50.01-50.01s-50.01 22.43-50.01 50.01c0 23.44 16.21 43.15 38.01 48.54v81.92h24v-34.98h30.73v-24h-30.73v-22.94zm-38.01-48.55c0-14.34 11.67-26.01 26.01-26.01s26.01 11.67 26.01 26.01-11.67 26.01-26.01 26.01-26.01-11.67-26.01-26.01z"/>
+            <linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="484.836" y1="475.674" x2="565.754"
+                            y2="615.828">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <circle cx="525.3" cy="545.75" r="80.9" style="fill:url(#SVGID_14_)"/>
+            <linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="482.787" y1="483.323" x2="559.605"
+                            y2="616.376">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#C6D5F4"/>
+            </linearGradient>
+            <circle cx="521.2" cy="549.85" r="76.81" style="fill:url(#SVGID_15_)"/>
+            <path class="st26"
+                  d="M538.5 547.62l23.01-23.01c4.44-4.44 4.44-11.63 0-16.06-4.44-4.44-11.63-4.44-16.06 0l-23.01 23.01-23.01-23.01c-4.44-4.44-11.63-4.44-16.06 0-4.44 4.44-4.44 11.63 0 16.06l23.01 23.01-23.01 23.01c-4.44 4.44-4.44 11.63 0 16.06 2.22 2.22 5.13 3.33 8.03 3.33 2.91 0 5.81-1.11 8.03-3.33l23.01-23.01 23.01 23.01c2.22 2.22 5.13 3.33 8.03 3.33s5.81-1.11 8.03-3.33c4.44-4.44 4.44-11.63 0-16.06l-23.01-23.01z"/>
+        </g>
+        <g>
+            <linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="232.569" y1="558.709" x2="232.569"
+                            y2="484.191">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M224.88 484.54s-18.08-2.5-23.95 5.81-8.02 29.58-8.02 29.58l13.61-.72-1.15 24.78 25.11 14.72 35.77-19.24-5.44-22.45 11.43-2.98s-3.4-32.58-19.31-27.77c-8.17.87-10.74.73-10.74.73s-2.15 6.85-9.53 6.27c-7.38-.59-7.78-8.73-7.78-8.73z"
+                  style="fill:url(#SVGID_16_)"/>
+            <linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="233.602" y1="471.483" x2="233.602"
+                            y2="495.089">
+                <stop offset="0" stop-color="#F4AE98"/>
+                <stop offset="1" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M226.69 474.3l-3.76 16.76c-.18.79.23 1.59.98 1.89 1.94.79 5.83 2.13 9.82 2.13 4.15 0 8.06-2.27 9.86-3.48.62-.42.88-1.19.64-1.9l-5.75-17.09a1.643 1.643 0 00-1.86-1.1l-8.61 1.53c-.65.11-1.18.61-1.32 1.26z"
+                  style="fill:url(#SVGID_17_)"/>
+            <linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="-816.068" y1="920.854" x2="-804.529"
+                            y2="839.612" gradientTransform="rotate(-8.082 -2795.015 -6505.71)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M204.24 487.44c5.26-1.75 12.4-.58 12.69 11.22s-11.28 30.62-7.13 37.16c4.2 6.63 13.17 16.05 18.89 21.41-1.33 6.3-4.91 11.61-4.91 11.61s-21.05-9.71-30.21-19.44c-9.17-9.73-4.54-32.03-.3-47.9 3.19-11.95 10.97-14.06 10.97-14.06z"
+                  style="fill:url(#SVGID_18_)"/>
+            <linearGradient id="SVGID_19_" gradientUnits="userSpaceOnUse" x1="-6575.898" y1="102.823" x2="-6564.359"
+                            y2="21.581" gradientTransform="scale(-1 1) rotate(-8.082 -118.103 -44396.273)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M259.39 487.44c-5.26-1.75-12.4-.58-12.69 11.22s11.28 30.62 7.13 37.16c-4.2 6.63-13.17 16.05-18.89 21.41 1.33 6.3 4.91 11.61 4.91 11.61s21.05-9.71 30.21-19.44c9.17-9.73 4.54-32.03.3-47.9-3.19-11.95-10.97-14.06-10.97-14.06z"
+                  style="fill:url(#SVGID_19_)"/>
+            <linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="232.569" y1="531.798" x2="232.569"
+                            y2="579.152">
+                <stop offset="0" stop-color="#275C89"/>
+                <stop offset="1" stop-color="#013F7C"/>
+            </linearGradient>
+            <path d="M206.79 579.15h51.1c2.31 0 4.38-1.75 5.19-4.4l10.3-33.89c1.34-4.4-1.33-9.07-5.19-9.07h-71.23c-3.82 0-6.48 4.6-5.21 8.98l9.84 33.89c.77 2.69 2.86 4.49 5.2 4.49z"
+                  style="fill:url(#SVGID_20_)"/>
+            <path class="st26"
+                  d="M204.75 594.74s-.79-1.74-1.4-1.93c-.61-.19-9.35-.54-12.53-1.36-3.19-.83-12.38-2.14-16.32 1.59-3.43 3.25-4.56 10.84.66 15.2 1.96 1.7 3.89 2.2 11.14 1.86 7.26-.34 17.78-.26 20.09-3.63-.07-5.55-1.64-11.73-1.64-11.73z"/>
+            <linearGradient id="SVGID_21_" gradientUnits="userSpaceOnUse" x1="-5720.751" y1="599.589" x2="-5703.986"
+                            y2="599.589" gradientTransform="matrix(-1 0 0 1 -5504.059 0)">
+                <stop offset="0" stop-color="#F4B9A4"/>
+                <stop offset=".652" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M212.86 592.81s-8.44 1.9-11.45 1.62-.49 11.87-.49 11.87 8.05.56 15.18-1.51c2.4-9.3-3.24-11.98-3.24-11.98z"
+                  style="fill:url(#SVGID_21_)"/>
+            <linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="209.839" y1="581.112" x2="296.322"
+                            y2="581.112">
+                <stop offset="0" stop-color="#18264B"/>
+                <stop offset=".652" stop-color="#2D3C65"/>
+            </linearGradient>
+            <path d="M209.84 592.37l4.39 13.64s94.25-12.41 80.78-43c-11.27-25.57-85.17 29.36-85.17 29.36z"
+                  style="fill:url(#SVGID_22_)"/>
+            <linearGradient id="SVGID_23_" gradientUnits="userSpaceOnUse" x1="190.339" y1="591.445" x2="190.339"
+                            y2="609.24">
+                <stop offset="0" stop-color="#FFDB80"/>
+                <stop offset="1" stop-color="#FFBB24"/>
+            </linearGradient>
+            <path d="M203.66 593.42s3.45 1.35 3.89 6.17c.44 4.82-.99 8.05-8.33 8.94s-9.21.56-13.81.67-11.29.56-12.27-8.2c-.99-8.75 7.96-10.98 17.24-8.75 2.92.56 13.28 1.17 13.28 1.17z"
+                  style="fill:url(#SVGID_23_)"/>
+            <g>
+                <path class="st26"
+                      d="M263.56 594.74s.79-1.74 1.4-1.93c.61-.19 9.35-.54 12.53-1.36 3.19-.83 11.75-2.2 16.08 1.49 4.01 3.42 4.27 11-.29 15.18-1.96 1.7-4.02 2.32-11.28 1.98-7.26-.34-17.78-.26-20.09-3.63.09-5.55 1.65-11.73 1.65-11.73z"/>
+                <linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="251.623" y1="599.589" x2="268.387"
+                                y2="599.589">
+                    <stop offset="0" stop-color="#F4B9A4"/>
+                    <stop offset=".652" stop-color="#FAD1BB"/>
+                </linearGradient>
+                <path d="M255.45 592.81s8.44 1.9 11.45 1.62.49 11.87.49 11.87-8.05.56-15.18-1.51c-2.4-9.3 3.24-11.98 3.24-11.98z"
+                      style="fill:url(#SVGID_24_)"/>
+                <linearGradient id="SVGID_25_" gradientUnits="userSpaceOnUse" x1="171.993" y1="581.112" x2="258.476"
+                                y2="581.112">
+                    <stop offset="0" stop-color="#445677"/>
+                    <stop offset="1" stop-color="#293861"/>
+                </linearGradient>
+                <path d="M258.48 592.37L254.09 606s-94.25-12.41-80.78-43c11.26-25.56 85.17 29.37 85.17 29.37z"
+                      style="fill:url(#SVGID_25_)"/>
+                <linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="277.976" y1="591.445" x2="277.976"
+                                y2="609.24">
+                    <stop offset="0" stop-color="#FFDB80"/>
+                    <stop offset="1" stop-color="#FFBB24"/>
+                </linearGradient>
+                <path d="M264.66 593.42s-3.45 1.35-3.89 6.17.99 8.05 8.33 8.94c7.34.89 9.21.56 13.81.67s11.29.56 12.27-8.2c.99-8.75-7.96-10.98-17.24-8.75-2.92.56-13.28 1.17-13.28 1.17z"
+                      style="fill:url(#SVGID_26_)"/>
+            </g>
+            <linearGradient id="SVGID_27_" gradientUnits="userSpaceOnUse" x1="249.053" y1="466.067" x2="218.202"
+                            y2="466.067">
+                <stop offset="0" stop-color="#F4B9A4"/>
+                <stop offset=".652" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M248.39 467.6c.56-.8.91-2.84.46-3.44-.83-.67-1.61-.28-2.21.3.14-4.88-.31-8.94-.41-9.97-.3-2.99-3.35-8.48-13.3-8.48-9.95 0-11.88 7.18-11.88 7.18s-.65 5.08-.46 11.24c-.59-.57-1.37-.93-2.18-.27-.46.6-.1 2.64.46 3.44.56.8.91 2.69 1.02 3.74.1.99-.62 3.65 2 3.31 1.56 6.25 7.89 11.47 11.82 11.47 4.3 0 10.01-5.26 11.63-11.48 2.68.37 1.95-2.31 2.04-3.31.09-1.04.45-2.93 1.01-3.73z"
+                  style="fill:url(#SVGID_27_)"/>
+            <linearGradient id="SVGID_28_" gradientUnits="userSpaceOnUse" x1="213.957" y1="454.142" x2="249.774"
+                            y2="454.142">
+                <stop offset="0" stop-color="#4F5C7C"/>
+                <stop offset="1" stop-color="#274168"/>
+            </linearGradient>
+            <path d="M240.1 443.88s-1.94-6.12-9.39-4.65c-7.44 1.46-7.95 4.98-10.87 5.12-4.99.23-8.97 6.45-2.58 13.03 2.85 2.93.44 4.19 1.79 6.78s1.34 5.12 1.34 5.12 2.38-7.6.81-10.84c-.81-1.67 2.77-2.13 7.24-1.73s11.51-1.08 12.06-4.12c1.32 6.23 2.64 6.88 4.31 7.83 1.68.95 1.78 8.48 1.78 8.48s.3-5.53 1.47-6.78c.96-2.04 2.85-10.07.72-12.02s-.32-8.19-8.68-6.22z"
+                  style="fill:url(#SVGID_28_)"/>
+        </g>
+    </g>
+</svg>

+ 197 - 0
common/src/assets/svgs/404.svg

@@ -0,0 +1,197 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" enable-background="new 0 0 800 800">
+    <style>.st49{fill:#d4e4fe}</style>
+    <g id="图层_5">
+        <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="401.193" y1="159.763" x2="401.193"
+                        y2="715.254">
+            <stop offset="0" stop-color="#F4F2FB"/>
+            <stop offset="1" stop-color="#E1EEF5"/>
+        </linearGradient>
+        <path d="M484.45 201.12c-38.37 30.29-120.74 33.81-181.17-2.22s-172-31.38-202.22 34.87 37.19 131.33 12.78 178.98S8.08 527.79 63.87 609.15s126.6 60.62 169.22 52.45c84.17-16.13 189.79 115.67 308.62 16.13 68.47-57.35 170.44 42.09 210.17-81.36 32.78-101.86-85.67-139.5-49.97-208.03 37.96-72.88 30.67-159.24-10.46-201.06-38.31-38.96-140.75-38.46-207 13.84z"
+              style="fill:url(#SVGID_1_)"/>
+        <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="484.537" y1="604.68" x2="484.537" y2="493.367">
+            <stop offset=".34" stop-color="#B0B9E1"/>
+            <stop offset=".866" stop-color="#EAF0F8"/>
+        </linearGradient>
+        <path d="M285.1 583.44c1.77-1.63 77.74-90.07 77.74-90.07h321.13l-99.5 111.31-299.37-21.24z"
+              style="fill:url(#SVGID_2_)"/>
+        <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="616.023" y1="627.266" x2="657.332"
+                        y2="555.716">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset=".866" stop-color="#EAF0F8"/>
+        </linearGradient>
+        <path d="M604.49 620.61L659.43 556.93 633.22 624.12z" style="fill:url(#SVGID_3_)"/>
+        <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="116.275" y1="540.149" x2="116.275"
+                        y2="402.974">
+            <stop offset=".003" stop-color="#9A9ADB"/>
+            <stop offset=".789" stop-color="#CECDF1"/>
+        </linearGradient>
+        <path d="M117.06 403.22s-.22-.57-.52.04c-2.7 5.49-27.15 64.96-29.09 110.86 0 0-4.08 26.37 30.11 26.02 28.54-.29 27.78-24.6 27.68-32.79-.39-33.22-28.18-104.13-28.18-104.13z"
+              style="fill:url(#SVGID_4_)"/>
+        <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="116.277" y1="418.206" x2="116.277" y2="569.34">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset=".818" stop-color="#AFB0E7"/>
+        </linearGradient>
+        <path d="M116.28 569.34c-.55 0-1-.45-1-1V419.21c0-.55.45-1 1-1s1 .45 1 1v149.13c0 .55-.45 1-1 1z"
+              style="fill:url(#SVGID_5_)"/>
+        <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="617.404" y1="448.627" x2="617.404"
+                        y2="360.303">
+            <stop offset=".227" stop-color="#CCD4F4"/>
+            <stop offset=".789" stop-color="#ECF1FB"/>
+        </linearGradient>
+        <path d="M617.91 360.46s-.14-.37-.33.03c-1.74 3.53-17.48 41.83-18.73 71.38 0 0-2.63 16.98 19.39 16.76 18.38-.18 17.89-15.84 17.82-21.11-.25-21.4-18.15-67.06-18.15-67.06z"
+              style="fill:url(#SVGID_6_)"/>
+        <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="617.405" y1="370.11" x2="617.405" y2="467.422">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset="1" stop-color="#A6A8E2"/>
+        </linearGradient>
+        <path d="M617.41 467.42c-.36 0-.64-.29-.64-.64v-96.02c0-.36.29-.64.64-.64.36 0 .64.29.64.64v96.02c0 .35-.29.64-.64.64z"
+              style="fill:url(#SVGID_7_)"/>
+        <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="463.322" y1="86.02" x2="428.568" y2="146.217">
+            <stop offset="0" stop-color="#FFDB80"/>
+            <stop offset="1" stop-color="#FFBB24"/>
+        </linearGradient>
+        <circle cx="445.95" cy="116.12" r="34.75" style="fill:url(#SVGID_8_)"/>
+        <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="420.985" y1="116.487" x2="420.985"
+                        y2="173.941">
+            <stop offset="0" stop-color="#F9FAFE"/>
+            <stop offset="1" stop-color="#E5EDF7"/>
+        </linearGradient>
+        <path d="M465.72 135.07h-34.57c-2.23-10.61-11.65-18.58-22.93-18.58s-20.69 7.97-22.93 18.58h-9.05c-10.73 0-19.44 8.7-19.44 19.44 0 10.73 8.7 19.44 19.44 19.44h89.47c10.73 0 19.44-8.7 19.44-19.44.01-10.74-8.69-19.44-19.43-19.44z"
+              style="fill:url(#SVGID_9_)"/>
+        <g>
+            <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="688.006" y1="537.867" x2="688.006"
+                            y2="510.039">
+                <stop offset=".227" stop-color="#AFB0E7"/>
+                <stop offset="1" stop-color="#ECF1FB"/>
+            </linearGradient>
+            <circle cx="688.01" cy="523.95" r="13.91" style="fill:url(#SVGID_10_)"/>
+            <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="688.056" y1="513.553" x2="688.056"
+                            y2="558.349">
+                <stop offset="0" stop-color="#DDE1F6"/>
+                <stop offset=".818" stop-color="#A6A8E2"/>
+            </linearGradient>
+            <path d="M688.06 558.35c-.24 0-.43-.19-.43-.43v-43.94c0-.24.19-.43.43-.43s.43.19.43.43v43.94a.44.44 0 01-.43.43z"
+                  style="fill:url(#SVGID_11_)"/>
+        </g>
+        <g>
+            <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="2879.853" y1="308.382" x2="2737.462"
+                            y2="450.774" gradientTransform="matrix(-1 0 0 1 3207.18 0)">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M270.73 392.79l91.4-73.3c7.43 11.92 20.65 19.87 35.7 19.87 16.43 0 30.69-9.48 37.6-23.26l92.11 76.85 10.83-12.98-98.5-82.19c0-.16.01-.31.01-.47 0-23.18-18.86-42.04-42.05-42.04-23.18 0-42.04 18.86-42.04 42.04 0 1.8.13 3.58.35 5.32l-95.98 76.97 10.57 13.19zm101.96-95.48c0-13.86 11.28-25.14 25.14-25.14s25.14 11.28 25.14 25.14-11.28 25.14-25.14 25.14-25.14-11.27-25.14-25.14z"
+                  style="fill:url(#SVGID_12_)"/>
+            <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="2814.247" y1="259.815" x2="2814.247"
+                            y2="392.836" gradientTransform="matrix(-1 0 0 1 3207.18 0)">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#C6D5F4"/>
+            </linearGradient>
+            <path d="M268.75 392.68l88.31-70.82c7.18 11.51 19.95 19.2 34.49 19.2 15.88 0 29.65-9.16 36.33-22.47l88.99 74.25 10.46-12.54-95.17-79.41c0-.15.01-.3.01-.46 0-22.4-18.22-40.62-40.62-40.62s-40.62 18.22-40.62 40.62c0 1.74.12 3.46.34 5.14l-92.73 74.37 10.21 12.74zm98.51-92.24c0-13.4 10.9-24.29 24.29-24.29 13.4 0 24.29 10.9 24.29 24.29 0 13.4-10.9 24.29-24.29 24.29-13.4 0-24.29-10.9-24.29-24.29z"
+                  style="fill:url(#SVGID_13_)"/>
+            <linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="2966.463" y1="329.794" x2="2654.707"
+                            y2="641.55" gradientTransform="matrix(-1 0 0 1 3203.43 0)">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M230.6 619.91h326.35c17.89 0 32.39-14.5 32.39-32.39V388.31c0-21.39-17.34-38.72-38.72-38.72H230.6c-17.89 0-32.39 14.5-32.39 32.39v205.54c-.01 17.88 14.5 32.39 32.39 32.39z"
+                  style="fill:url(#SVGID_14_)"/>
+            <linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="2716.773" y1="319.563" x2="2914.293"
+                            y2="661.678" gradientTransform="matrix(-1 0 0 1 3203.43 0)">
+                <stop offset="0" stop-color="#EBF2FA"/>
+                <stop offset=".525" stop-color="#FDFEFF"/>
+            </linearGradient>
+            <path d="M223.6 619.91h328.59c14.03 0 25.4-11.37 25.4-25.4V386.73c0-14.03-11.37-25.4-25.4-25.4H223.6c-14.03 0-25.4 11.37-25.4 25.4v207.78c0 14.03 11.38 25.4 25.4 25.4z"
+                  style="fill:url(#SVGID_15_)"/>
+            <linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="2815.495" y1="361.334" x2="2815.495"
+                            y2="425.526" gradientTransform="matrix(-1 0 0 1 3203.43 0)">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M198.24 425.53h379.39v-38.79c0-14.03-11.37-25.4-25.4-25.4H223.64c-14.03 0-25.4 11.37-25.4 25.4v38.79z"
+                  style="fill:url(#SVGID_16_)"/>
+            <linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="276.445" y1="488.742" x2="350.685"
+                            y2="531.604">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M328.82 457.46H307.7c-1.27 0-2.46.59-3.24 1.59L261.91 514c-.56.72-.86 1.6-.86 2.51v23.15c0 2.26 1.83 4.09 4.09 4.09h41.34c2.26 0 4.09 1.83 4.09 4.09v13.46c0 2.26 1.83 4.09 4.09 4.09h14.14c2.26 0 4.09-1.83 4.09-4.09v-13.46c0-2.26 1.83-4.09 4.09-4.09s4.09-1.83 4.09-4.09V525.5c0-2.26-1.83-4.09-4.09-4.09s-4.09-1.83-4.09-4.09v-55.77a4.059 4.059 0 00-4.07-4.09zm-39.3 57.35l13.74-17.74c2.39-3.08 7.33-1.4 7.33 2.51v17.74c0 2.26-1.83 4.09-4.09 4.09h-13.74c-3.41 0-5.33-3.91-3.24-6.6z"
+                  style="fill:url(#SVGID_17_)"/>
+            <linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="455.095" y1="488.742" x2="529.335"
+                            y2="531.604">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M511.56 517.32v-55.77c0-2.26-1.83-4.09-4.09-4.09h-21.12c-1.27 0-2.46.59-3.24 1.59L440.56 514c-.56.72-.86 1.6-.86 2.51v23.15c0 2.26 1.83 4.09 4.09 4.09h41.34c2.26 0 4.09 1.83 4.09 4.09v13.46c0 2.26 1.83 4.09 4.09 4.09h14.14c2.26 0 4.09-1.83 4.09-4.09v-13.46c0-2.26 1.83-4.09 4.09-4.09s4.09-1.83 4.09-4.09V525.5c0-2.26-1.83-4.09-4.09-4.09-2.24 0-4.07-1.83-4.07-4.09zm-43.39-2.51l13.74-17.74c2.39-3.08 7.33-1.4 7.33 2.51v17.74c0 2.26-1.83 4.09-4.09 4.09H471.4c-3.4 0-5.32-3.91-3.23-6.6z"
+                  style="fill:url(#SVGID_18_)"/>
+            <linearGradient id="SVGID_19_" gradientUnits="userSpaceOnUse" x1="339.488" y1="482.174" x2="441.31"
+                            y2="540.961">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M356.4 566.16h68c2.26 0 4.09-1.83 4.09-4.09v-101c0-2.26-1.83-4.09-4.09-4.09h-68c-2.26 0-4.09 1.83-4.09 4.09v101c0 2.26 1.83 4.09 4.09 4.09zm49.76-82.76v56.34c0 2.26-1.83 4.09-4.09 4.09h-23.34c-2.26 0-4.09-1.83-4.09-4.09V483.4c0-2.26 1.83-4.09 4.09-4.09h23.34c2.26 0 4.09 1.83 4.09 4.09z"
+                  style="fill:url(#SVGID_19_)"/>
+        </g>
+        <g>
+            <linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="871.514" y1="4485.232" x2="872.065"
+                            y2="4498.77" gradientTransform="rotate(2.333 95904.663 -3670.234)">
+                <stop offset="0" stop-color="#FFDB80"/>
+                <stop offset="1" stop-color="#FFBB24"/>
+            </linearGradient>
+            <path d="M605.95 610.6s3.25 4.88 10.55 1.06c3.91 2.72 8.92 4.97 12.39 5.88 3.47.91 3.68 5.4 3.12 6.61-4.66-.47-18.14.64-27.3-2.94.72-7.53 1.24-10.61 1.24-10.61z"
+                  style="fill:url(#SVGID_20_)"/>
+            <path class="st49"
+                  d="M604.06 623.84l.43-3.23s10.54 2.63 28.38 1.03c.17 1.66.35 2.48.35 2.48s-13.56 2.02-29.16-.28z"/>
+            <linearGradient id="SVGID_21_" gradientUnits="userSpaceOnUse" x1="-1427.263" y1="-235.579" x2="-1409.896"
+                            y2="-215.318" gradientTransform="rotate(40.6 -1575.457 2818.52)">
+                <stop offset="0" stop-color="#FFDB80"/>
+                <stop offset="1" stop-color="#FFBB24"/>
+            </linearGradient>
+            <path d="M520.47 596.12s-.05 5.81 7.27 7.94c1.95 5-3.73 11.79 5.37 12.42 3.34.23 1.75 5.12.73 5.63-10.95 4.01-14.63-10.12-19.62-18.98 4.32-5.09 6.25-7.01 6.25-7.01z"
+                  style="fill:url(#SVGID_21_)"/>
+            <linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="-3772.01" y1="604.486" x2="-3772.01"
+                            y2="502.198" gradientTransform="matrix(-1 0 0 1 -3222.68 0)">
+                <stop offset="0" stop-color="#445677"/>
+                <stop offset="1" stop-color="#293861"/>
+            </linearGradient>
+            <path d="M569.3 502.2s-14.44-.26-17.67 18.85c-3.23 19.11 1.57 23.66-5.38 37.29-3.62 7.1-27.15 41.12-27.15 41.12l6.83 5.03s37.94-34.72 43.52-48.71 9.83-28.83 10.13-41.46c.28-12.62-10.28-12.12-10.28-12.12z"
+                  style="fill:url(#SVGID_22_)"/>
+            <linearGradient id="SVGID_23_" gradientUnits="userSpaceOnUse" x1="-3839.642" y1="559.801" x2="-3786.238"
+                            y2="559.801" gradientTransform="matrix(-1 0 0 1 -3222.68 0)">
+                <stop offset="0" stop-color="#445677"/>
+                <stop offset="1" stop-color="#293861"/>
+            </linearGradient>
+            <path d="M572.72 506.19s14.87 3.53 15.75 3.98c.44.23 2.89 7.07 5.24 13.95 5.04 6.87 23.02 32.28 23.21 45.51.29 20.13-.96 43.67-.96 43.67l-9.24.11s-3.5-38.9-5.85-42.31c-.42-.61-1.29-1.95-2.42-3.74-5.14-6.22-16.5-16.65-28.16-27.07-16.45-14.66 2.43-34.1 2.43-34.1z"
+                  style="fill:url(#SVGID_23_)"/>
+            <linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="5317.908" y1="132.095" x2="5317.908"
+                            y2="56.817" gradientTransform="rotate(26.086 2112.504 -9908.036)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M603.14 448.91s-10.69-8.37-16.99-4.36c-6.3 4-14.27 18.91-14.27 18.91l8.85 4.38-23.8 39.67 40.69 21.83 14.6-42.28 11.79.69s7.96-25.24-3.62-27.43c-5.45-2.3-7.04-3.34-7.04-3.34s-3.49 4.27-7.99 1.18-2.22-9.25-2.22-9.25z"
+                  style="fill:url(#SVGID_24_)"/>
+            <linearGradient id="SVGID_25_" gradientUnits="userSpaceOnUse" x1="5161.945" y1="1134.369" x2="5171.26"
+                            y2="1068.78" gradientTransform="rotate(18.006 4848.87 -13687.47)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M589.15 443.6c3.88.61 8.04 4.05 4.56 12.85-3.48 8.8-16.66 18.5-16.06 24.82.6 6.4 3.37 16.58 5.33 22.6-2.8 4.17-6.72 6.78-6.72 6.78s-10.33-14.75-13.12-25.23 7.07-25.25 14.69-35.41c5.73-7.67 11.32-6.41 11.32-6.41z"
+                  style="fill:url(#SVGID_25_)"/>
+            <linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="-8924.659" y1="-865.525" x2="-8915.544"
+                            y2="-929.706" gradientTransform="scale(-1 1) rotate(-34.172 -2504.53 -13720.806)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M624.12 463.5c-2.79-3.19-7.68-4.9-11.53 3.69s-2.35 26.64-7.02 29.97c-4.72 3.37-13.34 7.07-18.62 8.96-1.12 5.12-.49 10.33-.49 10.33s16.36.44 25.19-3.42c8.83-3.86 12.82-21.97 15.06-35.2 1.69-9.97-2.59-14.33-2.59-14.33z"
+                  style="fill:url(#SVGID_26_)"/>
+            <linearGradient id="SVGID_27_" gradientUnits="userSpaceOnUse" x1="-3813.896" y1="480.898" x2="-3841.811"
+                            y2="423.883" gradientTransform="matrix(-1 0 0 1 -3222.68 0)">
+                <stop offset="0" stop-color="#4F5C7C"/>
+                <stop offset="1" stop-color="#274168"/>
+            </linearGradient>
+            <path d="M590.9 439.68c.43-4.69 4.5-7.9 9.3-7.17.4-1.31 4.44-2.98 5.38-4.6 3.5-6.03 9.26-7 14-3.56 9.79 2.79 8.01 12.2 4.75 21.55 2.8 5.61 1.52 12.41-.06 15.18 4.75 5.07 2.09 11.58-1.39 16.52-.4.56-.82 1.06-1.25 1.52-.21 5.85-8.34 7.86-11.32 4.89-3.17-3.16-3.57-4.49-9.32-1.76-5.75 2.73-11.24-1.54-11.3-7.34-.06-5.8-4.28-4.1-6.12-5.63-3.33-2.77-1.15-5.93-1.15-5.93s-4.85-.26-6.01-7.38c-1.33-16.99 11.95-17.08 14.49-16.29z"
+                  style="fill:url(#SVGID_27_)"/>
+            <path class="st49"
+                  d="M515.38 601.24s4.92 12.03 5.91 13.61 5.9 9.27 14.26 5.05c-.04 1.49-.11 2.43-.11 2.43s-9.42 6.26-15.33-4.62c-5.91-10.88-6.75-14.63-6.75-14.63l2.02-1.84z"/>
+        </g>
+    </g>
+</svg>

+ 325 - 0
common/src/assets/svgs/500.svg

@@ -0,0 +1,325 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" enable-background="new 0 0 800 800">
+    <style>.st26{fill:#fff}</style>
+    <g id="图层_16">
+        <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="402.832" y1="159.843" x2="402.832"
+                        y2="715.335">
+            <stop offset="0" stop-color="#F4F2FB"/>
+            <stop offset="1" stop-color="#E1EEF5"/>
+        </linearGradient>
+        <path d="M486.09 201.2c-38.37 30.29-120.74 33.81-181.17-2.22s-172-31.38-202.22 34.87 37.19 131.33 12.78 178.98S9.72 527.87 65.5 609.23s126.6 60.62 169.22 52.45c84.17-16.13 189.79 115.67 308.62 16.13 68.47-57.35 170.44 42.09 210.17-81.36 32.78-101.86-85.67-139.5-49.97-208.03 37.96-72.88 30.67-159.24-10.46-201.06-38.3-38.96-140.75-38.46-206.99 13.84z"
+              style="fill:url(#SVGID_1_)"/>
+        <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="117.913" y1="540.229" x2="117.913"
+                        y2="403.055">
+            <stop offset=".227" stop-color="#B7ACE0"/>
+            <stop offset=".789" stop-color="#E8E7FA"/>
+        </linearGradient>
+        <path d="M118.7 403.3s-.22-.57-.52.04c-2.7 5.49-27.15 64.96-29.09 110.86 0 0-4.08 26.37 30.11 26.02 28.54-.29 27.78-24.6 27.68-32.79-.39-33.22-28.18-104.13-28.18-104.13z"
+              style="fill:url(#SVGID_2_)"/>
+        <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="117.915" y1="418.287" x2="117.915" y2="569.42">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset=".818" stop-color="#AFB0E7"/>
+        </linearGradient>
+        <path d="M117.92 569.42c-.55 0-1-.45-1-1V419.29c0-.55.45-1 1-1s1 .45 1 1v149.13c0 .55-.45 1-1 1z"
+              style="fill:url(#SVGID_3_)"/>
+        <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="619.042" y1="448.707" x2="619.042"
+                        y2="360.383">
+            <stop offset=".227" stop-color="#CCD4F4"/>
+            <stop offset=".789" stop-color="#ECF1FB"/>
+        </linearGradient>
+        <path d="M619.55 360.54s-.14-.37-.33.03c-1.74 3.53-17.48 41.83-18.73 71.38 0 0-2.63 16.98 19.39 16.76 18.38-.18 17.89-15.84 17.82-21.11-.26-21.4-18.15-67.06-18.15-67.06z"
+              style="fill:url(#SVGID_4_)"/>
+        <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="619.043" y1="370.19" x2="619.043" y2="467.503">
+            <stop offset="0" stop-color="#ECF1FB"/>
+            <stop offset="1" stop-color="#A6A8E2"/>
+        </linearGradient>
+        <path d="M619.04 467.5c-.36 0-.64-.29-.64-.64v-96.02c0-.36.29-.64.64-.64s.64.29.64.64v96.02c.01.35-.28.64-.64.64z"
+              style="fill:url(#SVGID_5_)"/>
+        <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="464.96" y1="86.101" x2="430.206" y2="146.297">
+            <stop offset="0" stop-color="#FFDB80"/>
+            <stop offset="1" stop-color="#FFBB24"/>
+        </linearGradient>
+        <circle cx="447.58" cy="116.2" r="34.75" style="fill:url(#SVGID_6_)"/>
+        <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="422.623" y1="116.567" x2="422.623"
+                        y2="174.021">
+            <stop offset="0" stop-color="#F9FAFE"/>
+            <stop offset="1" stop-color="#E5EDF7"/>
+        </linearGradient>
+        <path d="M467.36 135.15h-34.57c-2.23-10.61-11.65-18.58-22.93-18.58s-20.69 7.97-22.93 18.58h-9.05c-10.73 0-19.44 8.7-19.44 19.44 0 10.73 8.7 19.44 19.44 19.44h89.47c10.73 0 19.44-8.7 19.44-19.44.01-10.74-8.7-19.44-19.43-19.44z"
+              style="fill:url(#SVGID_7_)"/>
+        <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="689.644" y1="537.948" x2="689.644"
+                        y2="510.119">
+            <stop offset=".227" stop-color="#AFB0E7"/>
+            <stop offset="1" stop-color="#ECF1FB"/>
+        </linearGradient>
+        <circle cx="689.64" cy="524.03" r="13.91" style="fill:url(#SVGID_8_)"/>
+        <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="689.694" y1="513.633" x2="689.694"
+                        y2="558.429">
+            <stop offset="0" stop-color="#DDE1F6"/>
+            <stop offset=".818" stop-color="#A6A8E2"/>
+        </linearGradient>
+        <path d="M689.69 558.43c-.24 0-.43-.19-.43-.43v-43.94c0-.24.19-.43.43-.43s.43.19.43.43V558c0 .24-.19.43-.43.43z"
+              style="fill:url(#SVGID_9_)"/>
+        <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="289.384" y1="477.19" x2="289.384"
+                        y2="411.226">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset="1" stop-color="#E7EFF7"/>
+        </linearGradient>
+        <path d="M202.07 451.28L270.1 411.23 376.7 411.23 315.15 477.19 237.41 476.01z" style="fill:url(#SVGID_10_)"/>
+        <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="454.145" y1="502.809" x2="454.145"
+                        y2="420.65">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset="1" stop-color="#E7EFF7"/>
+        </linearGradient>
+        <path d="M386.71 479.55L431.76 420.65 521.58 420.65 423.81 502.81 394.37 495.15z" style="fill:url(#SVGID_11_)"/>
+        <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="589.016" y1="472.132" x2="589.016"
+                        y2="397.68">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset="1" stop-color="#E7EFF7"/>
+        </linearGradient>
+        <path d="M501.26 458.64l64.79-60.96h110.72l-48.99 66.61a19.243 19.243 0 01-17.85 7.7l-108.67-13.35z"
+              style="fill:url(#SVGID_12_)"/>
+        <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="314.267" y1="607.349" x2="314.267"
+                        y2="497.361">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset="1" stop-color="#E7EFF7"/>
+        </linearGradient>
+        <path d="M212.23 592.77L303.67 497.36 416.3 497.36 297.04 607.35 247.57 604.7z" style="fill:url(#SVGID_13_)"/>
+        <g>
+            <linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="515.604" y1="312.867" x2="613.092"
+                            y2="481.721">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M564.35 296.53c-41.79 0-75.67 33.6-75.67 75.05v51.43c0 41.45 33.88 75.05 75.67 75.05s75.67-33.6 75.67-75.05v-51.43c-.01-41.45-33.88-75.05-75.67-75.05zm23.82 137.83c0 13.05-10.67 23.63-23.82 23.63-13.16 0-23.82-10.58-23.82-23.63v-74.13c0-13.05 10.67-23.63 23.82-23.63 13.16 0 23.82 10.58 23.82 23.63v74.13z"
+                  style="fill:url(#SVGID_14_)"/>
+            <linearGradient id="SVGID_15_" gradientUnits="userSpaceOnUse" x1="513.839" y1="321.619" x2="606.64"
+                            y2="482.355">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M560.24 305.91c-39.52 0-71.56 32.04-71.56 71.56v49.03c0 39.52 32.04 71.56 71.56 71.56s71.56-32.04 71.56-71.56v-49.03c0-39.52-32.04-71.56-71.56-71.56zm22.53 131.41c0 12.44-10.09 22.53-22.53 22.53-12.44 0-22.53-10.09-22.53-22.53v-70.67c0-12.44 10.09-22.53 22.53-22.53 12.44 0 22.53 10.09 22.53 22.53v70.67z"
+                  style="fill:url(#SVGID_15_)"/>
+            <linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="217.031" y1="307.363" x2="316.583"
+                            y2="479.793">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M333.72 412.6c-5.55-58.15-65.99-54.01-90.14-49.98l2.26-15.28 71.49 5.88 8.98-5.88V307.2h-109l-9.09 7.47-14.81 92.41h43.6c22.73-19.99 38.77-11.37 45.38 0 6.34 10.92 7.27 43.26-19.71 43.87-23.34.53-23.13-19.92-23.13-19.92l-41.55.58-8.06 7.52s6.18 59.41 69.73 59.41 77.3-50.09 74.05-85.94z"
+                  style="fill:url(#SVGID_16_)"/>
+            <linearGradient id="SVGID_17_" gradientUnits="userSpaceOnUse" x1="212.735" y1="311.982" x2="309.699"
+                            y2="479.928">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M324.26 415.94c-5.19-55.89-61.65-51.92-84.21-48.04l2.11-14.69h75.17v-38.58H208.14l-14.95 96h40.73c21.23-19.21 36.22-10.93 42.39 0 5.92 10.49 6.79 46.38-18.41 46.97-21.8.51-24.41-19.14-24.41-19.14l-43.54.66s5.78 59.41 65.14 59.41 72.2-48.14 69.17-82.59z"
+                  style="fill:url(#SVGID_17_)"/>
+            <linearGradient id="SVGID_18_" gradientUnits="userSpaceOnUse" x1="368.459" y1="304.731" x2="452.448"
+                            y2="450.205">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M387.26 461.26s-54.09-36.72-56.49-83.83c-2.29-45.03 25.47-81.27 76.27-81.27 55.29 0 78.12 47.95 78.12 73.99 0 26.04-10.63 63.25-55.73 93.35-23.53 0-42.17-2.24-42.17-2.24z"
+                  style="fill:url(#SVGID_18_)"/>
+            <linearGradient id="SVGID_19_" gradientUnits="userSpaceOnUse" x1="366.623" y1="312.428" x2="445.175"
+                            y2="448.483">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M384.76 461.29s-51.7-34.94-53.99-79.77c-2.19-42.85 24.35-77.34 72.9-77.34 52.85 0 73.47 45.54 73.47 70.32 0 24.78-12.03 58.72-55.14 87.36-22.49.01-37.24-.57-37.24-.57z"
+                  style="fill:url(#SVGID_19_)"/>
+            <linearGradient id="SVGID_20_" gradientUnits="userSpaceOnUse" x1="400.418" y1="454.748" x2="417.994"
+                            y2="485.191">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M414.59 486.78h-16.64c-.85 0-1.64-.44-2.08-1.17l-11.39-18.8c-.7-1.15-.33-2.64.82-3.34 1.15-.69 2.64-.33 3.34.82l10.68 17.62h13.84l10.6-19.05c.65-1.17 2.13-1.6 3.31-.94 1.17.65 1.6 2.13.94 3.31l-11.29 20.3c-.44.77-1.25 1.25-2.13 1.25z"
+                  style="fill:url(#SVGID_20_)"/>
+            <linearGradient id="SVGID_21_" gradientUnits="userSpaceOnUse" x1="397.841" y1="454.748" x2="415.417"
+                            y2="485.191">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M412.01 486.78h-16.64c-.85 0-1.64-.44-2.08-1.17l-11.39-18.8c-.7-1.15-.33-2.64.82-3.34 1.15-.69 2.64-.33 3.34.82l10.68 17.62h13.84l10.6-19.05c.65-1.17 2.13-1.6 3.31-.94 1.17.65 1.6 2.13.94 3.31l-11.29 20.3c-.43.77-1.25 1.25-2.13 1.25z"
+                  style="fill:url(#SVGID_21_)"/>
+            <linearGradient id="SVGID_22_" gradientUnits="userSpaceOnUse" x1="395.626" y1="441.888" x2="415.816"
+                            y2="476.856">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M429.22 468.35h-47.66c-2.76 0-5-2.24-5-5V452.9h57.65v10.45c0 2.76-2.23 5-4.99 5z"
+                  style="fill:url(#SVGID_22_)"/>
+            <linearGradient id="SVGID_23_" gradientUnits="userSpaceOnUse" x1="395.022" y1="445.756" x2="412.776"
+                            y2="476.507">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M425.57 468.35h-44.01c-2.76 0-5-2.24-5-5v-6.93h54.01v6.93c0 2.76-2.24 5-5 5z"
+                  style="fill:url(#SVGID_23_)"/>
+            <linearGradient id="SVGID_24_" gradientUnits="userSpaceOnUse" x1="396.171" y1="472.261" x2="416.697"
+                            y2="507.813">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M418.79 505.46h-25.7c-4.09 0-7.4-3.31-7.4-7.4v-19.75h40.5v19.75c0 4.09-3.31 7.4-7.4 7.4z"
+                  style="fill:url(#SVGID_24_)"/>
+            <linearGradient id="SVGID_25_" gradientUnits="userSpaceOnUse" x1="395.099" y1="476.159" x2="413.018"
+                            y2="507.195">
+                <stop offset=".116" stop-color="#DEE4FF"/>
+                <stop offset=".847" stop-color="#BACBEE"/>
+            </linearGradient>
+            <path d="M414.04 505.46h-20.95c-4.09 0-7.4-3.31-7.4-7.4v-16.47h35.75v16.47c0 4.09-3.31 7.4-7.4 7.4z"
+                  style="fill:url(#SVGID_25_)"/>
+            <linearGradient id="SVGID_26_" gradientUnits="userSpaceOnUse" x1="370.752" y1="345.042" x2="439.366"
+                            y2="413.656">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M404.4 311.4s-17.23 79.51 1.33 135.9c47.84-62.43-1.33-135.9-1.33-135.9z"
+                  style="fill:url(#SVGID_26_)"/>
+            <linearGradient id="SVGID_27_" gradientUnits="userSpaceOnUse" x1="352.936" y1="350.49" x2="415.513"
+                            y2="413.067">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M386.43 316.99s-15.24 26.94-16.34 62.72c-.75 24.43 11.93 66.85 11.93 66.85s-20.76-36.07-20.76-70.23 25.17-59.34 25.17-59.34z"
+                  style="fill:url(#SVGID_27_)"/>
+            <linearGradient id="SVGID_28_" gradientUnits="userSpaceOnUse" x1="389.798" y1="347.846" x2="456.792"
+                            y2="414.84">
+                <stop offset="0" stop-color="#C8CBF2"/>
+                <stop offset="1" stop-color="#AFB0E7"/>
+            </linearGradient>
+            <path d="M420.65 316.99s34.1 22.12 34.1 60.99-29.68 68.58-29.68 68.58 23.5-42.18 23.5-70.9c0-14.24-13.98-48.76-27.92-58.67z"
+                  style="fill:url(#SVGID_28_)"/>
+            <path class="st26"
+                  d="M386.43 316.99s-62.13 47.12-4.42 129.57c-7.06-15.6-36.21-73.62 4.42-129.57zM420.65 316.99s62.13 47.12 4.42 129.57c7.07-15.6 36.22-73.62-4.42-129.57zM404.4 311.4s-35.48 79.66 1.33 135.9c32.24-57.5-1.33-135.9-1.33-135.9z"/>
+        </g>
+        <g>
+            <linearGradient id="SVGID_29_" gradientUnits="userSpaceOnUse" x1="234.692" y1="561.708" x2="234.692"
+                            y2="486.088">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M226.89 486.45s-18.35-2.54-24.31 5.89c-5.96 8.43-8.14 30.01-8.14 30.01l13.81-.73-1.16 25.14 25.48 14.94 36.3-19.52-5.52-22.78 11.6-3.03s-3.46-33.06-19.59-28.18c-8.29.89-10.9.74-10.9.74s-2.18 6.95-9.67 6.36c-7.49-.58-7.9-8.84-7.9-8.84z"
+                  style="fill:url(#SVGID_29_)"/>
+            <linearGradient id="SVGID_30_" gradientUnits="userSpaceOnUse" x1="235.741" y1="473.191" x2="235.741"
+                            y2="497.147">
+                <stop offset="0" stop-color="#F4AE98"/>
+                <stop offset="1" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M228.72 476.05l-3.81 17.01c-.18.8.24 1.61 1 1.92 1.97.8 5.91 2.17 9.97 2.17 4.21 0 8.18-2.3 10-3.53.63-.42.89-1.21.65-1.93l-5.83-17.35a1.681 1.681 0 00-1.89-1.12l-8.74 1.55c-.67.11-1.2.62-1.35 1.28z"
+                  style="fill:url(#SVGID_30_)"/>
+            <linearGradient id="SVGID_31_" gradientUnits="userSpaceOnUse" x1="-1535.437" y1="750.954" x2="-1523.728"
+                            y2="668.51" gradientTransform="rotate(-8.082 -1929.216 -11692.611)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M205.94 489.39c5.34-1.77 12.58-.59 12.88 11.39.29 11.98-11.45 31.07-7.24 37.71 4.26 6.73 13.37 16.29 19.17 21.73-1.35 6.4-4.99 11.78-4.99 11.78s-21.36-9.86-30.66-19.73c-9.3-9.87-4.61-32.5-.3-48.61 3.24-12.13 11.14-14.27 11.14-14.27z"
+                  style="fill:url(#SVGID_31_)"/>
+            <linearGradient id="SVGID_32_" gradientUnits="userSpaceOnUse" x1="-5585.118" y1="175.804" x2="-5573.409"
+                            y2="93.36" gradientTransform="scale(-1 1) rotate(-8.082 -118.041 -37329.02)">
+                <stop offset="0" stop-color="#C3D5FD"/>
+                <stop offset="1" stop-color="#1A90FC"/>
+            </linearGradient>
+            <path d="M261.91 489.39c-5.34-1.77-12.58-.59-12.88 11.39-.29 11.98 11.45 31.07 7.24 37.71-4.26 6.73-13.37 16.29-19.17 21.73 1.35 6.4 4.99 11.78 4.99 11.78s21.36-9.86 30.66-19.73c9.3-9.87 4.61-32.5.3-48.61-3.24-12.13-11.14-14.27-11.14-14.27z"
+                  style="fill:url(#SVGID_32_)"/>
+            <linearGradient id="SVGID_33_" gradientUnits="userSpaceOnUse" x1="234.692" y1="534.399" x2="234.692"
+                            y2="582.454">
+                <stop offset="0" stop-color="#275C89"/>
+                <stop offset="1" stop-color="#013F7C"/>
+            </linearGradient>
+            <path d="M208.53 582.45h51.85c2.35 0 4.45-1.78 5.26-4.46l10.45-34.39c1.36-4.46-1.35-9.21-5.26-9.21h-72.29c-3.87 0-6.58 4.67-5.29 9.11l9.98 34.39c.8 2.74 2.92 4.56 5.3 4.56z"
+                  style="fill:url(#SVGID_33_)"/>
+            <path class="st26"
+                  d="M206.46 598.27s-.8-1.76-1.42-1.95c-.62-.19-9.49-.54-12.72-1.38s-12.56-2.17-16.56 1.61c-3.48 3.3-4.63 11 .67 15.43 1.99 1.73 3.94 2.23 11.31 1.89s18.04-.27 20.38-3.68c-.07-5.65-1.66-11.92-1.66-11.92z"/>
+            <linearGradient id="SVGID_34_" gradientUnits="userSpaceOnUse" x1="-3991.106" y1="603.193" x2="-3974.093"
+                            y2="603.193" gradientTransform="matrix(-1 0 0 1 -3772.525 0)">
+                <stop offset="0" stop-color="#F4B9A4"/>
+                <stop offset=".652" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M214.69 596.31s-8.56 1.92-11.62 1.64c-3.06-.28-.5 12.05-.5 12.05s8.17.57 15.4-1.53c2.45-9.44-3.28-12.16-3.28-12.16z"
+                  style="fill:url(#SVGID_34_)"/>
+            <linearGradient id="SVGID_35_" gradientUnits="userSpaceOnUse" x1="211.625" y1="584.443" x2="299.388"
+                            y2="584.443">
+                <stop offset="0" stop-color="#18264B"/>
+                <stop offset=".652" stop-color="#2D3C65"/>
+            </linearGradient>
+            <path d="M211.63 595.87l4.45 13.84s95.64-12.6 81.97-43.63c-11.43-25.96-86.42 29.79-86.42 29.79z"
+                  style="fill:url(#SVGID_35_)"/>
+            <linearGradient id="SVGID_36_" gradientUnits="userSpaceOnUse" x1="191.837" y1="594.929" x2="191.837"
+                            y2="612.987">
+                <stop offset="0" stop-color="#FFDB80"/>
+                <stop offset="1" stop-color="#FFBB24"/>
+            </linearGradient>
+            <path d="M205.35 596.94s3.5 1.37 3.95 6.26c.44 4.89-1 8.17-8.45 9.07-7.45.91-9.34.57-14.01.68-4.67.11-11.45.57-12.46-8.32-1-8.88 8.08-11.15 17.5-8.88 2.96.56 13.47 1.19 13.47 1.19z"
+                  style="fill:url(#SVGID_36_)"/>
+            <g>
+                <path class="st26"
+                      d="M266.14 598.27s.8-1.76 1.42-1.95c.62-.19 9.49-.54 12.72-1.38 3.23-.84 11.93-2.24 16.32 1.51 4.07 3.48 4.34 11.16-.3 15.4-1.99 1.73-4.08 2.35-11.44 2.01s-18.04-.27-20.38-3.68c.08-5.64 1.66-11.91 1.66-11.91z"/>
+                <linearGradient id="SVGID_37_" gradientUnits="userSpaceOnUse" x1="254.028" y1="603.193" x2="271.04"
+                                y2="603.193">
+                    <stop offset="0" stop-color="#F4B9A4"/>
+                    <stop offset=".652" stop-color="#FAD1BB"/>
+                </linearGradient>
+                <path d="M257.92 596.31s8.56 1.92 11.62 1.64c3.06-.28.5 12.05.5 12.05s-8.17.57-15.4-1.53c-2.45-9.44 3.28-12.16 3.28-12.16z"
+                      style="fill:url(#SVGID_37_)"/>
+                <linearGradient id="SVGID_38_" gradientUnits="userSpaceOnUse" x1="173.22" y1="584.443" x2="260.983"
+                                y2="584.443">
+                    <stop offset="0" stop-color="#445677"/>
+                    <stop offset="1" stop-color="#293861"/>
+                </linearGradient>
+                <path d="M260.98 595.87l-4.45 13.84s-95.64-12.6-81.97-43.63c11.43-25.96 86.42 29.79 86.42 29.79z"
+                      style="fill:url(#SVGID_38_)"/>
+                <linearGradient id="SVGID_39_" gradientUnits="userSpaceOnUse" x1="280.771" y1="594.929" x2="280.771"
+                                y2="612.987">
+                    <stop offset="0" stop-color="#FFDB80"/>
+                    <stop offset="1" stop-color="#FFBB24"/>
+                </linearGradient>
+                <path d="M267.26 596.94s-3.5 1.37-3.95 6.26 1 8.17 8.45 9.07 9.34.57 14.01.68 11.45.57 12.46-8.32c1-8.88-8.08-11.15-17.5-8.88-2.96.56-13.47 1.19-13.47 1.19z"
+                      style="fill:url(#SVGID_39_)"/>
+            </g>
+            <linearGradient id="SVGID_40_" gradientUnits="userSpaceOnUse" x1="251.42" y1="467.696" x2="220.113"
+                            y2="467.696">
+                <stop offset="0" stop-color="#F4B9A4"/>
+                <stop offset=".652" stop-color="#FAD1BB"/>
+            </linearGradient>
+            <path d="M250.74 469.25c.57-.81.93-2.88.46-3.49-.84-.68-1.63-.29-2.24.3.14-4.96-.31-9.07-.42-10.12-.31-3.04-3.4-8.6-13.5-8.6s-12.05 7.29-12.05 7.29-.66 5.15-.46 11.41c-.6-.58-1.39-.95-2.22-.28-.46.61-.1 2.68.46 3.49.57.81.93 2.73 1.03 3.79.1 1.01-.63 3.7 2.03 3.36 1.59 6.35 8.01 11.64 11.99 11.64 4.36 0 10.16-5.33 11.8-11.65 2.71.37 1.98-2.34 2.07-3.35.13-1.06.49-2.98 1.05-3.79z"
+                  style="fill:url(#SVGID_40_)"/>
+            <linearGradient id="SVGID_41_" gradientUnits="userSpaceOnUse" x1="215.804" y1="455.594" x2="252.152"
+                            y2="455.594">
+                <stop offset="0" stop-color="#4F5C7C"/>
+                <stop offset="1" stop-color="#274168"/>
+            </linearGradient>
+            <path d="M242.34 445.19s-1.97-6.21-9.53-4.72c-7.55 1.48-8.06 5.06-11.03 5.19-5.06.24-9.11 6.54-2.61 13.22 2.89 2.97.45 4.25 1.82 6.88s1.36 5.19 1.36 5.19 2.41-7.71.82-11c-.82-1.7 2.82-2.16 7.35-1.75s11.68-1.1 12.24-4.18c1.34 6.32 2.68 6.98 4.38 7.94 1.7.96 1.8 8.6 1.8 8.6s.3-5.62 1.49-6.88c.98-2.07 2.89-10.22.73-12.19s-.34-8.31-8.82-6.3z"
+                  style="fill:url(#SVGID_41_)"/>
+        </g>
+        <linearGradient id="SVGID_42_" gradientUnits="userSpaceOnUse" x1="509.948" y1="612.061" x2="509.948"
+                        y2="547.57">
+            <stop offset="0" stop-color="#B0B9E1"/>
+            <stop offset="1" stop-color="#E7EFF7"/>
+        </linearGradient>
+        <path d="M452.67 596.16L498.32 547.57 567.22 547.57 506.27 612.06z" style="fill:url(#SVGID_42_)"/>
+        <linearGradient id="SVGID_43_" gradientUnits="userSpaceOnUse" x1="461.835" y1="563.724" x2="495.632"
+                        y2="622.263">
+            <stop offset="0" stop-color="#C8CBF2"/>
+            <stop offset="1" stop-color="#AFB0E7"/>
+        </linearGradient>
+        <circle cx="478.73" cy="592.99" r="33.79" style="fill:url(#SVGID_43_)"/>
+        <linearGradient id="SVGID_44_" gradientUnits="userSpaceOnUse" x1="455.798" y1="564.313" x2="489.595"
+                        y2="622.851">
+            <stop offset=".116" stop-color="#DEE4FF"/>
+            <stop offset=".847" stop-color="#BACBEE"/>
+        </linearGradient>
+        <circle cx="472.7" cy="593.58" r="33.79" style="fill:url(#SVGID_44_)"/>
+        <linearGradient id="SVGID_45_" gradientUnits="userSpaceOnUse" x1="479.001" y1="231.35" x2="503.267" y2="273.38">
+            <stop offset="0" stop-color="#C8CBF2"/>
+            <stop offset="1" stop-color="#AFB0E7"/>
+        </linearGradient>
+        <circle cx="491.13" cy="252.36" r="24.26" style="fill:url(#SVGID_45_)"/>
+        <linearGradient id="SVGID_46_" gradientUnits="userSpaceOnUse" x1="474.666" y1="231.772" x2="498.933"
+                        y2="273.803">
+            <stop offset=".116" stop-color="#DEE4FF"/>
+            <stop offset=".847" stop-color="#BACBEE"/>
+        </linearGradient>
+        <circle cx="486.8" cy="252.79" r="24.26" style="fill:url(#SVGID_46_)"/>
+    </g>
+</svg>

+ 13 - 0
common/src/assets/svgs/del.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg t="1722242333836" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1347"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+    <path d="M808.65 395.1l-10.54-89.6c-2.95-25.08-24.2-43.97-49.45-43.97H637.69c-10.14 0-18.36-8.22-18.36-18.36 0-34.73-28.15-62.88-62.88-62.88h-81.23c-34.73 0-62.88 28.15-62.88 62.88 0 10.14-8.22 18.36-18.36 18.36H283.01c-25.25 0-46.5 18.9-49.45 43.97l-10.54 89.6c-3.48 29.6 19.65 55.61 49.45 55.61h486.74c29.81 0 52.93-26.01 49.45-55.61z"
+          fill="#4384F7" p-id="1348"></path>
+    <path d="M671.71 849.29H359.96c-26.37 0-48.37-20.14-50.7-46.4L274.87 415.1c-2.64-29.77 20.81-55.39 50.7-55.39H706.1c29.88 0 53.34 25.63 50.7 55.39l-34.39 387.79c-2.33 26.26-24.33 46.4-50.7 46.4z"
+          fill="#6CA4F4" p-id="1349"></path>
+    <path d="M389.13 486m40.38 0l0.01 0q40.38 0 40.38 40.38l0 157.13q0 40.38-40.38 40.38l-0.01 0q-40.38 0-40.38-40.38l0-157.13q0-40.38 40.38-40.38Z"
+          fill="#FFFFFF" p-id="1350"></path>
+    <path d="M558.13 486m40.38 0l0.01 0q40.38 0 40.38 40.38l0 157.13q0 40.38-40.38 40.38l-0.01 0q-40.38 0-40.38-40.38l0-157.13q0-40.38 40.38-40.38Z"
+          fill="#FFFFFF" p-id="1351"></path>
+</svg>

+ 7 - 0
common/src/assets/svgs/guize.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg t="1725352774800" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2944"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+    <path d="M1021.804463 27.927237a38.27051 38.27051 0 0 0-37.531258-27.920982h-0.227462C780.35337 1.314163 285.168226 33.272598 85.228979 305.374218c-104.063943 141.310873-112.707505 324.815978-26.613074 545.226821 0.568655 1.762832 1.137311 3.809991 2.957009 4.321781l5.459092 1.307907 44.810047-92.690834c71.479987-132.837907 193.172248-300.477526 390.609411-424.615005a40.033342 40.033342 0 0 1 53.510475 9.95147 35.08604 35.08604 0 0 1-10.57699 50.439736c-222.969792 140.514755-343.638473 344.263994-401.755058 473.519372l127.378815 27.693519c27.181729 2.04716 54.363459 3.070739 81.829516 3.070739 130.620151 0 283.247267-24.054124 388.96031-116.460631 83.933541-73.242819 126.29837-178.7284 126.29837-313.613466 0-331.469247 132.610445-404.14341 133.349697-404.6552a35.711561 35.711561 0 0 0 20.357864-40.94319zM65.155443 860.097584c-30.707393 71.878046-42.649157 119.758832-43.217812 121.06674-3.525664 19.163688 9.780873 37.872451 29.911275 41.966771 20.41473 4.094319 40.431401-7.676848 45.606165-26.66994 0.227462-1.251042 13.306537-50.667198 45.890493-123.341361l-76.597886-16.661604-1.592235 3.639394z"
+          p-id="2945"></path>
+</svg>

+ 3 - 0
common/src/assets/svgs/icon.svg

@@ -0,0 +1,3 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
+    <path d="M115.147.062a13 13 0 014.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 012.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/>
+</svg>

+ 188 - 0
common/src/assets/svgs/login-box-bg.svg

@@ -0,0 +1,188 @@
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 700 700" xml:space="preserve"
+     enable-background="new 0 0 700 700"><style>.st0{fill:#e5e6eb}.st1{fill:#fff}.st2{fill:#84a9ff}.st3{fill:#050f64}.st4{fill:#155bcd}.st5{fill:#ffbd00}.st6{fill:#ff654f}.st9{fill:#f5bdc8}.st10{fill:#ea8096}.st11{opacity:0}.st13{fill:#dca000}</style>
+    <path class="st0"
+          d="M101.8 176.7c21.4-19.8 48.8-33.2 77.8-37.2 92.4-12.6 158.2 78.1 240.3 104.9 40.8 13.3 85.4 12.6 125.4 28 68.5 26.2 131.4 117.8 101 191.6-23.7 57.5-79.6 71.8-134.6 54-33.5-10.9-64.1-29.4-97.6-40.5-38.1-12.6-78.7-15.1-118.9-16.7s-80.6-2.4-119.6-12-77-28.9-101.2-60.9C40.8 343.4 48 260.8 73.1 213.7c7.4-13.9 17.2-26.3 28.7-37z"/>
+    <path class="st1"
+          d="M82 257.1c5.7-23.2 18.9-44.7 37.3-60.4l1.7-1.5 1.8-1.4 1.8-1.4 1.8-1.3c.6-.4 1.2-.9 1.8-1.3l1.9-1.3c.6-.4 1.2-.9 1.9-1.3l1.9-1.2c5.1-3.2 10.5-6 16.1-8.4 11.1-4.7 23-7.8 35.1-9 12.1-1.1 24.3-.5 36.1 1.5 5.9 1 11.8 2.4 17.6 4 .7.2 1.5.4 2.2.6l2.2.7 2.2.7 2.1.7 2.1.7 2.1.8 2.1.8 2.1.8c5.6 2.2 11.1 4.6 16.5 7.2 5.4 2.6 10.7 5.4 15.9 8.3 10.4 5.9 20.6 12.2 30.5 18.8-10.4-5.9-20.7-11.8-31.4-17.2-5.3-2.7-10.7-5.3-16.1-7.7-5.4-2.4-10.9-4.7-16.5-6.7l-2.1-.8-2.1-.7-2.1-.7-2.1-.7-2.1-.7-2.1-.6-2.1-.6-2.1-.6c-5.7-1.5-11.5-2.8-17.3-3.7-11.6-1.9-23.5-2.5-35.2-1.3-11.7 1.1-23.2 4-34.1 8.5-5.4 2.2-10.7 4.9-15.8 7.9l-1.9 1.1c-.6.4-1.2.8-1.9 1.2l-1.8 1.2c-.6.4-1.2.8-1.8 1.3l-1.8 1.3-1.8 1.3-1.8 1.3-1.7 1.4c-18.2 15.2-32 35.7-39.1 58.4z"/>
+    <path class="st2"
+          d="M183.1 543.2c-.3 1.2-.5 1.8-.5 1.8-.7-.5-1.4-.9-2.1-1.4-120.8-82.8-72.6-232.2-72.6-232.2 115.7 67.3 80.1 213.8 75.2 231.8z"/>
+    <path class="st3"
+          d="M183.1 543.2c-.3 1.2-.5 1.8-.5 1.8-.7-.5-1.4-.9-2.1-1.4-10.1-29.9-20.1-59.8-29.8-89.8-5-15.5-10-31.1-14.8-46.7l-3.6-11.7-3.5-11.7c-1.2-3.9-2.2-7.8-3.4-11.8-.6-2-1.1-3.9-1.6-5.9l-1.6-5.9 1.6 5.9c.5 2 1.1 3.9 1.7 5.9 1.2 3.9 2.3 7.8 3.5 11.7l3.6 11.7 3.7 11.7c5 15.5 10.2 31 15.4 46.5 10.4 30 20.8 59.9 31.4 89.7zM137.9 384.9c-.1 0-.2 0-.4-.1-.3-.1-.4-.5-.2-.8 3.7-7.2 6-15.3 6.7-23.4 0-.3.3-.5.6-.5s.5.3.5.6c-.7 8.2-3.1 16.5-6.9 23.8 0 .3-.2.4-.3.4zM154 430.5h-.3c-.3-.1-.4-.5-.3-.7 3.4-8.3 7.6-16.4 12.3-24.1.2-.3.5-.3.8-.2.3.2.3.5.2.8-4.7 7.6-8.8 15.6-12.2 23.9-.1.1-.3.3-.5.3zM137.4 440.3h-.3c-9.5-3.9-18.3-9.3-26.1-16.1-.2-.2-.3-.6-.1-.8.2-.2.6-.3.8-.1 7.7 6.7 16.3 12 25.7 15.9.3.1.4.5.3.7 0 .2-.1.3-.3.4zM125.9 390.5c-.2.1-.4.1-.6-.1l-19.2-15c-.2-.2-.3-.6-.1-.8.2-.2.6-.3.8-.1l19.2 15c.2.2.3.6.1.8 0 .1-.1.2-.2.2zM170.7 478.4h-.3c-.3-.1-.4-.5-.3-.7l10.1-23.5c.1-.3.5-.4.7-.3.3.1.4.5.3.7l-10.1 23.5c0 .1-.2.3-.4.3zM151.6 481.6h-.3l-24.3-10c-.3-.1-.4-.5-.3-.7.1-.3.5-.4.7-.3l24.3 10c.3.1.4.5.3.7-.1.1-.3.3-.4.3z"/>
+    <path class="st4"
+          d="M182.3 543.2c.3 1.2.4 1.9.4 1.9-.8-.1-1.7-.2-2.5-.3C35 525 11 369.8 11 369.8c133.5 8.2 167.5 155.1 171.3 173.4z"/>
+    <path class="st1"
+          d="M182.3 543.2c.3 1.2.4 1.9.4 1.9-.8-.1-1.7-.2-2.5-.3-22.5-22.1-44.8-44.4-66.9-66.8-11.5-11.6-22.9-23.3-34.2-35.1l-8.5-8.8-8.4-8.9c-2.8-3-5.5-6-8.3-9-1.4-1.5-2.7-3-4.1-4.6l-4-4.6 4.1 4.5c1.4 1.5 2.7 3 4.1 4.5 2.8 3 5.6 6 8.4 8.9l8.5 8.8 8.6 8.7c11.5 11.6 23 23.1 34.7 34.6 22.5 22.2 45.2 44.3 68.1 66.2zM70.7 422.1c-.1.1-.2.1-.3.1-.3 0-.6-.3-.6-.6.1-8.1-1.5-16.4-4.5-23.9-.1-.3 0-.6.3-.7.3-.1.6 0 .7.3 3 7.7 4.6 16.1 4.6 24.4 0 .1-.1.3-.2.4zM105.6 455.5c-.1.1-.2.1-.3.1-.3 0-.6-.2-.6-.5-.7-9-.6-18.1.2-27 0-.3.3-.5.6-.5s.5.3.5.6c-.8 8.9-.9 17.9-.2 26.8.1.2 0 .4-.2.5zM95.2 471.7c-.1.1-.2.1-.3.1-10.3.8-20.5-.1-30.5-2.7-.3-.1-.5-.4-.4-.7.1-.3.4-.5.7-.4 9.9 2.5 20 3.4 30.1 2.6.3 0 .6.2.6.5 0 .4-.1.5-.2.6zM62.6 432.4c-.1.1-.3.2-.5.2l-23.9-4.8c-.3-.1-.5-.4-.4-.7.1-.3.4-.5.7-.4l23.9 4.8c.3.1.5.4.4.7-.1.1-.1.2-.2.2zM142.1 490.8c-.1.1-.2.1-.3.1-.3 0-.6-.2-.6-.5l-1.5-25.5c0-.3.2-.6.5-.6s.6.2.6.5l1.5 25.5c0 .2-.1.4-.2.5zM126.4 502.3c-.1.1-.2.1-.3.1l-26.2 2c-.3 0-.6-.2-.6-.5s.2-.6.5-.6l26.2-2c.3 0 .6.2.6.5 0 .2-.1.4-.2.5z"/>
+    <g><path class="st5" d="M259.6 503.3c1.2.5 1.8.7 1.8.7-.5.7-1.1 1.3-1.7 1.9C164 616.8 20.9 552.3 20.9 552.3c79.7-107.4 221.4-55.9 238.7-49z"/>
+        <path class="st1"
+              d="M259.6 503.3c1.2.5 1.8.7 1.8.7-.5.7-1.1 1.3-1.7 1.9-30.8 6.8-61.6 13.3-92.5 19.7-16 3.3-32 6.5-48 9.6l-12 2.3-12 2.2c-4 .7-8 1.4-12.1 2-2 .4-4 .6-6 .9l-6.1.9 6-1c2-.3 4-.6 6-1 4-.7 8-1.4 12-2.2l12-2.3 12-2.4c16-3.3 31.9-6.7 47.9-10.2 31-6.9 61.9-13.9 92.7-21.1zM97.3 530.8c0 .1 0 .2-.1.3-.2.3-.5.3-.8.2-6.8-4.5-14.6-7.7-22.5-9.3-.3-.1-.5-.4-.4-.7.1-.3.4-.5.7-.4 8.1 1.6 16 4.9 22.9 9.5.1 0 .1.2.2.4zM144.3 519.7c0 .1 0 .2-.1.3-.2.3-.5.4-.8.2-7.9-4.3-15.5-9.4-22.5-14.9-.2-.2-.3-.6-.1-.8.2-.2.6-.3.8-.1 7 5.5 14.6 10.5 22.4 14.8.2.1.3.3.3.5zM152.2 537.3c0 .1 0 .2-.1.3-4.9 9-11.3 17.2-18.8 24.1-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8 7.5-6.9 13.7-14.9 18.6-23.8.2-.3.5-.4.8-.2.2.1.3.3.3.4zM101.5 543.2c.1.2 0 .4-.1.6l-17 17.5c-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8l17-17.5c.2-.2.6-.2.8 0 .1 0 .1.1.1.2zM193.8 508.4c0 .1 0 .2-.1.3-.2.3-.5.4-.8.2l-22.2-12.7c-.3-.2-.4-.5-.2-.8.2-.3.5-.4.8-.2l22.2 12.7c.2.2.3.3.3.5zM194.9 527.8c0 .1 0 .2-.1.3l-12.7 23.1c-.2.3-.5.4-.8.2-.3-.2-.4-.5-.2-.8l12.7-23.1c.2-.3.5-.4.8-.2.1.2.3.3.3.5z"/></g>
+    <g><path class="st2" d="M608.8 430.3c-1 .2-2.4-.3-4.4-1.4-3.2-1.9-8.3-4.9-10.2-6.1 3 6.3 5.8 12.7 8.3 19.2 4.5-1 7.9-.1 10.1 1.4 2.2 1.5 3.3 3.6 3.3 4.6-.1 2-1.8 2.4-4.9.3-1.6-1.1-3.7-2.6-5.5-3.9-1.3-.9-2.3-1.7-2.8-2 .8 2 1.5 4 2.2 6h.2c1.3.2 3.1 3.1 3.9 4.1 1.7 2.3 3 4.9 3.2 7.8.1 1.2-.1 2.6-1.2 3.2-1.2.6-2.6-.3-3.5-1.3-2.5-2.8-4-6.5-4.1-10.2 0-1-.1-3.3 1.2-3.5-.8-2-1.5-3.9-2.3-5.9-.1.6-.4 1.9-.7 3.4-.5 2.1-1.1 4.7-1.7 6.4-1.1 3.5-2.7 4.1-4 2.8-.7-.7-1.1-2.7-.3-5.2.8-2.4 2.6-5.3 6.6-7.7-2.7-6.4-5.6-12.7-8.8-18.9-.1.8-.3 2.2-.5 3.7-.3 2.6-.9 5.7-1.4 7.8-.5 2.1-1.2 3.4-2 4-.8.6-1.7.4-2.5-.3-.9-.7-1.6-3.1-.9-6.2.6-2.9 2.6-6.5 7-9.6-3.5-6.6-7.2-13.1-11.2-19.4v.3c0 1 0 2.5-.1 4.1-.1 1.6-.2 3.4-.3 5-.1 1.7-.4 3.3-.5 4.6-.8 5.3-3 6.6-5.2 5-1.2-.8-2.1-3.7-1.7-7.4.2-1.9.9-4 2.2-6.2 1.1-2 2.8-4.2 5.2-6.3-3.8-5.8-7.8-11.5-12-17 .1 1.2.2 2.8.2 4.6.1 1.8.1 3.9.1 5.8v2.8c0 .9-.1 1.8-.1 2.5-.4 6.1-2.8 7.8-5.5 6.2-.7-.4-1.4-1.4-1.9-2.8s-.8-3.3-.7-5.4c.1-2.2.7-4.6 1.9-7.3 1.1-2.4 2.8-5 5.2-7.6-4.2-5.4-8.5-10.5-13.1-15.5l2-1.8c4.5 5.2 8.8 10.5 12.9 16 3.1-1.6 6.1-2.5 8.8-2.7 3-.3 5.6.1 7.8.9s4 1.9 5.3 3.1c1.2 1.2 2 2.4 2.2 3.3.7 3.5-2 4.7-8 2.5-3.1-1.2-7.3-2.8-10.7-4.2-1.7-.6-3.3-1.2-4.4-1.6 4.1 5.6 8 11.5 11.6 17.4 2.9-1.2 5.6-1.7 8-1.8 2.6 0 4.8.5 6.7 1.4 3.8 1.7 5.8 4.5 6 6 .3 3.1-2 4-7.1 1.6-2.6-1.3-6.1-3-9-4.4-1.4-.7-2.8-1.3-3.7-1.8-.1 0-.1-.1-.2-.1 3.9 6.4 7.5 13 10.8 19.8 5.1-1.6 9.2-.9 12 .7 2.8 1.6 4.3 4 4.4 5.2-.7 1.1-1.2 1.8-2.2 2z"/></g>
+    <g><path class="st2" d="M552.1 373.7c-.5 1.1-.8 1.7-.8 1.7l-1.8-1.8c-105.3-101.8-32.8-241.1-32.8-241.1 102.7 85.7 43.2 224.2 35.4 241.2z"/>
+        <path class="st1"
+              d="M552.1 373.7c-.5 1.1-.8 1.7-.8 1.7l-1.8-1.8c-5-31.1-9.8-62.3-14.4-93.5-2.4-16.1-4.7-32.3-6.8-48.5l-1.6-12.1-1.5-12.2c-.5-4.1-.9-8.1-1.4-12.2-.2-2-.4-4.1-.6-6.1l-.5-6.1.6 6.1c.2 2 .4 4.1.7 6.1.5 4 1 8.1 1.5 12.1l1.6 12.1 1.7 12.1c2.4 16.1 4.9 32.3 7.5 48.4 5.1 31.4 10.3 62.7 15.8 93.9zM533.9 210c-.1 0-.2 0-.3-.1-.3-.2-.3-.5-.1-.8 4.9-6.5 8.5-14.1 10.6-21.9.1-.3.4-.5.7-.4.3.1.5.4.4.7-2.1 8-5.8 15.7-10.7 22.3-.3.1-.5.2-.6.2zM542.2 257.6c-.1 0-.2 0-.3-.1-.3-.2-.3-.5-.2-.8 4.8-7.6 10.2-14.9 16.2-21.7.2-.2.6-.3.8-.1.2.2.3.6.1.8-5.9 6.7-11.3 13.9-16.1 21.5-.1.3-.3.4-.5.4zM524.2 264.5c-.1 0-.2 0-.3-.1-8.7-5.4-16.5-12.2-23-20.2-.2-.2-.2-.6.1-.8.2-.2.6-.2.8.1 6.4 7.9 14.1 14.6 22.7 19.9.3.2.3.5.2.8-.2.2-.4.3-.5.3zM521.2 213.5c-.2 0-.4 0-.5-.2l-16.5-18c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0l16.5 18c.2.2.2.6 0 .8-.1.2-.2.2-.3.2zM550.7 307.7c-.1 0-.2 0-.3-.1-.3-.2-.3-.5-.2-.8l13.9-21.5c.2-.3.5-.3.8-.2.3.2.3.5.2.8l-13.9 21.5c-.1.2-.3.3-.5.3zM531.2 307.6c-.1 0-.2 0-.3-.1l-22.3-13.9c-.3-.2-.3-.5-.2-.8.2-.3.5-.3.8-.2l22.3 13.9c.3.2.3.5.2.8-.1.2-.3.3-.5.3z"/>
+        <g><path class="st4" d="M526.6 382.8c-1 .7-1.6 1-1.6 1-.2-.8-.4-1.6-.6-2.5-35-142.2 100.5-221.5 100.5-221.5 41.5 127.2-82.7 212.8-98.3 223z"/>
+            <path class="st3"
+                  d="M526.6 382.8c-1 .7-1.6 1-1.6 1-.2-.8-.4-1.6-.6-2.5 12.3-29 24.8-58 37.5-86.8 6.6-14.9 13.3-29.8 20-44.7l5.1-11.1 5.2-11.1c1.7-3.7 3.6-7.3 5.3-11 .9-1.8 1.8-3.6 2.7-5.5l2.8-5.4-2.7 5.5c-.9 1.8-1.8 3.6-2.7 5.5-1.7 3.7-3.5 7.4-5.2 11.1l-5.1 11.1-5 11.2c-6.6 14.9-13 29.9-19.4 44.9-12.2 29.2-24.3 58.5-36.3 87.8zM598.2 234.5c-.1-.1-.2-.2-.2-.3-.1-.3 0-.6.3-.7 7.6-2.9 14.7-7.4 20.6-13 .2-.2.6-.2.8 0 .2.2.2.6 0 .8-6 5.7-13.3 10.2-21 13.2-.1.1-.3.1-.5 0zM580 279.3c-.1-.1-.2-.1-.2-.2-.1-.3 0-.6.3-.8 8.1-3.9 16.6-7.2 25.2-9.7.3-.1.6.1.7.4.1.3-.1.6-.4.7-8.6 2.5-17 5.8-25 9.7-.3 0-.5 0-.6-.1zM561 275.5c-.1-.1-.2-.1-.2-.3-4.5-9.3-7.4-19.1-8.7-29.3 0-.3.2-.6.5-.6s.6.2.6.5c1.3 10.1 4.2 19.8 8.6 29 .1.3 0 .6-.3.8-.1 0-.3 0-.5-.1zM585.6 230.8c-.2-.1-.3-.2-.4-.4l-4.4-24c-.1-.3.1-.6.5-.7.3-.1.6.1.7.5l4.4 24c.1.3-.1.6-.5.7-.1-.1-.2-.1-.3-.1zM560.5 326.2c-.1-.1-.2-.1-.2-.2-.1-.3 0-.6.3-.8l23.2-10.8c.3-.1.6 0 .8.3.1.3 0 .6-.3.8l-23.2 10.8c-.2 0-.4 0-.6-.1zM544.1 315.8c-.1-.1-.2-.1-.2-.2l-11.5-23.7c-.1-.3 0-.6.3-.8.3-.1.6 0 .8.3l11.5 23.7c.1.3 0 .6-.3.8-.2 0-.5 0-.6-.1z"/></g>
+        <g><path class="st5" d="M482.2 415.1c-1.2 0-1.9-.1-1.9-.1l.9-2.4C532.4 275.4 689 286.2 689 286.2c-37.4 128.5-188.2 129.3-206.8 128.9z"/>
+            <path class="st1"
+                  d="M482.2 415.1c-1.2 0-1.9-.1-1.9-.1l.9-2.4c26.5-17 53.2-33.9 79.9-50.6 13.9-8.6 27.8-17.2 41.7-25.6l10.5-6.3 10.5-6.2c3.5-2.1 7.1-4.1 10.6-6.1 1.8-1 3.6-2 5.3-3l5.4-2.9-5.3 3c-1.8 1-3.6 2-5.3 3-3.5 2.1-7 4.1-10.5 6.2l-10.5 6.4-10.4 6.4c-13.8 8.6-27.6 17.4-41.4 26.2-26.6 17.2-53.1 34.5-79.5 52zM624.9 333c0-.1-.1-.2 0-.4.1-.3.4-.5.7-.4 7.9 1.8 16.3 2.2 24.3.9.3 0 .6.2.7.5 0 .3-.2.6-.5.7-8.2 1.3-16.7 1-24.8-.9l-.4-.4zM584.6 359.7c0-.1-.1-.2 0-.3 0-.3.3-.5.6-.5 8.9 1.3 17.8 3.4 26.3 6.2.3.1.5.4.4.7-.1.3-.4.5-.7.4-8.5-2.7-17.3-4.8-26.1-6.1-.3-.1-.4-.3-.5-.4zM571.1 345.9c-.1-.1-.1-.2-.1-.3 1.5-10.2 4.6-20 9.3-29.2.1-.3.5-.4.8-.2.3.1.4.5.2.8-4.6 9.1-7.7 18.7-9.2 28.8 0 .3-.3.5-.6.5-.2-.2-.4-.3-.4-.4zM616.6 322.8c-.1-.2-.1-.4-.1-.6l9.9-22.3c.1-.3.5-.4.8-.3.3.1.4.5.3.8l-9.9 22.3c-.1.3-.5.4-.8.3l-.2-.2zM542.1 387.4c0-.1-.1-.2 0-.3.1-.3.3-.5.7-.5l25.2 4.2c.3.1.5.3.5.7-.1.3-.3.5-.7.5l-25.2-4.2c-.3-.1-.4-.2-.5-.4zM534.4 369.6c0-.1-.1-.2 0-.3l3.9-26c0-.3.3-.5.6-.5s.5.3.5.6l-3.9 26c0 .3-.3.5-.6.5s-.4-.2-.5-.3z"/></g></g>
+    <g><path class="st2" d="M445 229c-.1 1 .4 2.4 1.6 4.3 2.1 3.1 5.4 8 6.6 9.9-6.4-2.7-13-5.1-19.6-7.3.8-4.5-.3-7.9-1.9-10-1.6-2.2-3.7-3.1-4.8-3-2 .2-2.3 1.9 0 4.9 1.1 1.5 2.8 3.5 4.2 5.3 1 1.2 1.8 2.2 2.2 2.7-2-.7-4.1-1.3-6.1-1.9v-.2c-.3-1.3-3.3-3-4.3-3.7-2.4-1.6-5.1-2.7-7.9-2.8-1.2 0-2.6.3-3.1 1.4-.5 1.2.5 2.6 1.5 3.4 2.9 2.4 6.7 3.6 10.4 3.5 1 0 3.3 0 3.5-1.4 2 .7 4 1.3 6 2-.6.2-1.9.5-3.4.9-2.1.6-4.6 1.4-6.3 2-3.4 1.3-4 2.9-2.6 4.2.7.6 2.8 1 5.2 0 2.4-.9 5.2-2.9 7.3-7 6.6 2.3 13 4.9 19.3 7.7-.8.1-2.2.4-3.7.7-2.5.5-5.6 1.2-7.7 1.8-2.1.6-3.3 1.4-3.9 2.2-.5.8-.4 1.7.4 2.5s3.2 1.4 6.2.5c2.9-.8 6.3-2.9 9.3-7.5 6.8 3.1 13.5 6.5 20 10.2h-.3c-1 .1-2.5.2-4.1.4-1.6.2-3.3.4-5 .6-1.7.2-3.3.5-4.6.8-5.2 1.1-6.4 3.3-4.7 5.5.9 1.1 3.8 1.9 7.5 1.3 1.9-.3 4-1.1 6.1-2.5 2-1.3 4-3 6-5.5 6 3.4 11.9 7.1 17.7 11.1h-4.6c-1.8 0-3.9.1-5.8.2-1 .1-1.9.1-2.8.2-.9.1-1.7.2-2.5.3-6.1.8-7.6 3.2-5.9 5.8.4.7 1.5 1.3 2.9 1.7 1.5.4 3.3.7 5.5.4 2.2-.3 4.6-.9 7.2-2.3 2.3-1.2 4.8-3 7.3-5.6 5.6 3.9 11 8 16.2 12.3l1.7-2.1c-5.4-4.2-11-8.2-16.7-12 1.4-3.2 2.1-6.2 2.2-8.9.1-3-.4-5.6-1.3-7.7-.9-2.1-2.1-3.9-3.4-5.1-1.3-1.2-2.5-1.9-3.4-2-3.5-.5-4.6 2.2-2 8.1 1.4 3 3.2 7.1 4.8 10.5.7 1.7 1.4 3.2 1.9 4.3-5.9-3.8-11.9-7.4-18-10.7 1-3 1.4-5.7 1.3-8.1-.1-2.6-.8-4.8-1.7-6.6-1.9-3.7-4.8-5.5-6.3-5.6-3.1-.2-3.9 2.3-1.2 7.2 1.4 2.5 3.3 5.9 4.9 8.8.8 1.4 1.5 2.7 2 3.6 0 .1.1.1.1.2-6.6-3.5-13.4-6.8-20.3-9.7 1.3-5.2.4-9.2-1.3-11.9-1.8-2.8-4.2-4.1-5.4-4.1-1.6.3-2.3.8-2.4 1.8z"/></g>
+    <g><path class="st2" d="M100.2 255.8c1-.1 2.4.5 4.3 1.8 3 2.2 7.8 5.7 9.6 7-2.4-6.5-4.6-13.2-6.4-19.9-4.6.6-7.9-.6-9.9-2.3-2.1-1.7-3-3.9-2.8-4.9.3-2 2-2.2 4.9.2 1.5 1.2 3.4 3 5.1 4.4 1.2 1 2.2 1.9 2.6 2.3-.6-2.1-1.1-4.1-1.6-6.2h-.2c-1.3-.3-2.8-3.4-3.5-4.5-1.5-2.4-2.5-5.2-2.5-8 0-1.2.4-2.6 1.5-3 1.3-.5 2.6.6 3.4 1.6 2.3 3 3.3 6.8 3.1 10.5-.1 1-.2 3.3-1.5 3.4.6 2 1.2 4.1 1.8 6.1.2-.6.6-1.9 1-3.4.7-2.1 1.6-4.5 2.3-6.2 1.4-3.4 3.1-3.9 4.3-2.4.6.7.8 2.8-.2 5.2-1 2.4-3.1 5-7.3 7 2 6.6 4.4 13.2 6.9 19.6.2-.8.5-2.2.8-3.7.6-2.5 1.4-5.6 2.1-7.6.7-2.1 1.5-3.3 2.4-3.8.9-.5 1.7-.3 2.5.5s1.2 3.3.3 6.2c-.9 2.8-3.2 6.2-7.8 8.9 2.8 6.9 5.9 13.8 9.3 20.4v-.3c.1-1 .3-2.4.5-4s.5-3.3.8-5c.3-1.7.7-3.3 1-4.6 1.3-5.2 3.6-6.3 5.7-4.5 1.1.9 1.8 3.9 1 7.5-.4 1.8-1.3 3.9-2.8 6-1.3 1.9-3.2 3.9-5.8 5.8 3.2 6.2 6.6 12.2 10.3 18.1 0-1.2.1-2.8.2-4.6.1-1.8.3-3.9.5-5.8.1-1 .2-1.9.3-2.8.1-.9.3-1.7.4-2.5 1-6 3.5-7.5 6.1-5.6.6.5 1.2 1.5 1.6 3 .3 1.5.5 3.3.2 5.5s-1.1 4.5-2.6 7.1c-1.3 2.3-3.2 4.7-5.9 7 3.6 5.7 7.5 11.3 11.6 16.7l-2.2 1.6c-4-5.6-7.8-11.3-11.3-17.2-3.3 1.3-6.3 1.9-9 1.9-3 0-5.6-.6-7.7-1.6-2.1-1-3.8-2.3-4.9-3.6-1.1-1.3-1.8-2.6-1.8-3.4-.3-3.5 2.4-4.5 8.2-1.7 2.9 1.5 7 3.5 10.3 5.2 1.7.8 3.1 1.5 4.2 2-3.6-6-6.9-12.2-9.9-18.5-3 .9-5.7 1.2-8.1 1-2.6-.2-4.7-1-6.5-2-3.6-2-5.3-5-5.4-6.6 0-3.1 2.4-3.8 7.2-.9 2.4 1.5 5.8 3.5 8.6 5.2 1.4.9 2.6 1.6 3.5 2.1.1 0 .1.1.2.1-3.3-6.8-6.2-13.7-8.8-20.7-5.3 1.1-9.2 0-11.9-1.8-2.7-1.9-3.9-4.4-3.8-5.6-.1-.9.5-1.6 1.5-1.7z"/></g>
+    <g><path class="st4" d="M106.8 558.3c0 13.1 8.1 23.7 18.2 23.7h455c10.1 0 18.2-10.6 18.2-23.7H106.8z"/>
+        <path class="st2" d="M155.4 290.9H549.6V538.5H155.4z"/>
+        <path class="st3"
+              d="M556.6 264.8h-408c-7.6 0-13.8 6.2-13.8 13.8V540c0 7.6 6.2 13.8 13.8 13.8h408c7.6 0 13.8-6.2 13.8-13.8V278.6c0-7.7-6.2-13.8-13.8-13.8z"/>
+        <path class="st1" d="M155.4 285.5H549.6V533.1H155.4z"/>
+        <path class="st0"
+              d="M295.7 558.3L196.6 558.3 196.9 553.9 197.3 548.4 294.9 548.4zM508.6 558.3L409.4 558.3 409.8 553.9 410.2 548.4 507.8 548.4z"/></g>
+    <g><path class="st0" d="M188 451.7H222.4V455.59999999999997H188zM235 451.7H269.4V455.59999999999997H235zM328.6 451.7H353.5V455.59999999999997H328.6zM374.8 451.7H413.5V455.59999999999997H374.8zM342.3 465.1H359.90000000000003V469H342.3zM342.3 475.3H359.90000000000003V479.2H342.3zM342.3 485.6H359.90000000000003V489.5H342.3zM342.3 495.8H359.90000000000003V499.7H342.3z"/>
+        <path class="st6" d="M209.7 465.1H222.39999999999998V469H209.7z"/>
+        <path class="st2" d="M209.7 475.3H222.39999999999998V479.2H209.7z"/>
+        <path class="st4" d="M209.7 485.6H222.39999999999998V489.5H209.7z"/>
+        <path class="st5" d="M209.7 495.8H222.39999999999998V499.7H209.7z"/>
+        <path class="st0"
+              d="M399.7 465.1H417.3V469H399.7zM399.7 475.3H417.3V479.2H399.7zM399.7 485.6H417.3V489.5H399.7zM399.7 495.8H417.3V499.7H399.7zM234.6 465.1H252.2V469H234.6zM234.6 475.3H260.7V479.2H234.6zM234.6 485.6H267.5V489.5H234.6zM234.6 495.8H249.7V499.7H234.6zM180.4 314.6H306V321.5H180.4z"/>
+        <path class="st4"
+              d="M180.4 340.4H198.20000000000002V347.9H180.4zM216.1 340.4H233.9V347.9H216.1zM251.8 340.4H269.6V347.9H251.8zM287.5 340.4H305.3V347.9H287.5zM323.3 340.4H341.1V347.9H323.3zM359 340.4H376.8V347.9H359zM394.7 340.4H412.5V347.9H394.7z"/>
+        <g><path class="st0" d="M180.4 355.7H430.20000000000005V358H180.4z"/></g>
+        <g><path class="st0" d="M427.7 446.2H181v-90.4h-2v92.5h250.7v-92.5h-2v90.4z"/>
+            <path class="st0"
+                  d="M405.1 355.7H407.1V447.2H405.1zM382.4 355.7H384.5V447.2H382.4zM359.8 355.7H361.8V447.2H359.8zM337.2 355.7H339.2V447.2H337.2zM314.6 355.7H316.6V447.2H314.6zM292 355.7H294V447.2H292zM269.4 355.7H271.4V447.2H269.4zM246.8 355.7H248.8V447.2H246.8zM224.2 355.7H226.2V447.2H224.2zM201.6 355.7H203.7V447.2H201.6z"/>
+            <path class="st0"
+                  d="M179 355.7H429.7V357.7H179zM180 378.4H428.7V380.4H180zM180 401H428.7V403H180zM180 423.6H428.7V425.6H180z"/>
+            <g><path class="st2" d="M203.6 396.2H219.79999999999998V446.3H203.6zM248.8 385.8H265V446.3H248.8zM294.1 410.5H310.3V446.3H294.1zM339.3 373.7H355.5V446.29999999999995H339.3zM384.5 393.3H400.7V446.3H384.5z"/></g>
+            <g><path class="st6" d="M201.6 396.2H217.79999999999998V446.3H201.6zM246.8 385.8H263V446.3H246.8zM292 410.5H308.2V446.3H292zM337.2 373.7H353.4V446.29999999999995H337.2zM382.5 393.3H398.7V446.3H382.5z"/></g></g>
+        <g><path class="st0" d="M179 471.1H429.7V473.20000000000005H179z"/></g>
+        <g><path class="st0" d="M179 481.3H429.7V483.40000000000003H179z"/></g>
+        <g><path class="st0" d="M179 491.6H429.7V493.70000000000005H179z"/></g>
+        <g><path class="st0" d="M179 501.8H429.7V503.90000000000003H179z"/></g>
+        <g><path class="st6" d="M473.5 352.4c.9-5.5 5.4-9.8 10.9-10.6l-.2-5.1-.5-12.6c-14.7 1.2-26.4 12.7-27.9 27.2l12.6.8 5.1.3z"/>
+            <path class="st5"
+                  d="M491.1 366.7c-1.5.6-3.1.9-4.8.9-2.9 0-5.6-.9-7.7-2.5l-3.5 3.8-8.5 9.2c5.3 4.5 12.2 7.2 19.7 7.2 4.7 0 9.1-1 13-2.9l-5.9-11.1-2.3-4.6zM516.3 361.3l-12.4-2.1c-1.2 4.6-4 8.4-7.9 10.9l5.9 11.1 2.7 5.2c8.8-5.1 15.3-13.8 17.5-24.1l-5.8-1z"/>
+            <path class="st6"
+                  d="M468.2 354.9l-12.6-.8-5.9-.4v.9c0 10.1 4.1 19.3 10.7 25.9l4-4.3 8.5-9.2c-2.8-3.2-4.6-7.4-4.7-12.1z"/>
+            <path class="st4"
+                  d="M495.9 339.3l-2.4 4.6c3.5 2.3 5.7 6.3 5.7 10.8v.8l5.1.9 12.4 2.1c.2-1.3.2-2.5.2-3.8 0-11.3-6.1-21.2-15.2-26.5l-5.8 11.1zM487 336.6c2.3.1 4.4.6 6.4 1.4l5.8-11.2 2.7-5.2c-4.7-2.3-10-3.5-15.7-3.5l.2 5.9.6 12.6z"/></g>
+        <g><path class="st0" d="M446.7 407.2H525.1V411.09999999999997H446.7zM446.7 441.5H450.59999999999997V445.4H446.7zM454.4 441.5H525.1V445.4H454.4z"/>
+            <path class="st4" d="M446.7 456.1H450.59999999999997V460H446.7z"/>
+            <path class="st0" d="M454.4 456.1H525.1V460H454.4z"/>
+            <path d="M446.7 470.8H450.59999999999997V474.7H446.7z" style="fill:#6292ff"/>
+            <path class="st0" d="M454.4 470.8H525.1V474.7H454.4z"/>
+            <path d="M446.7 485.4H450.59999999999997V489.29999999999995H446.7z" style="fill:#da5544"/>
+            <path class="st0" d="M454.4 485.4H525.1V489.29999999999995H454.4z"/>
+            <path class="st5" d="M446.7 500H450.59999999999997V503.9H446.7z"/>
+            <path class="st0" d="M454.4 500H525.1V503.9H454.4zM446.7 417.7H525.1V430.7H446.7z"/></g></g>
+    <g><path class="st3" d="M522.8 556.7c.3-.3.7-.5 1.1-.6.4-.1.8-.1 1.3-.1 1-.1 2-.3 2.9-.8.5-.3.9-.6 1.4-.8l2.9.1c.4.4.7 1 .8 1.6.1.5.1 1.1.1 1.6v.6h-10.8v-.6c0-.4 0-.8.3-1z"/>
+        <path class="st9" d="M532.7 551.2L532.4 554.5 529.4 554.4 529.2 551.4z"/>
+        <path class="st3"
+              d="M494 555.5c.3-.3.7-.4 1.1-.5.4 0 .8 0 1.3.1 1 .1 2.1-.1 3-.5.5-.2 1-.5 1.5-.6l2.9.4c.4.5.5 1.1.6 1.7.1.5 0 1.1-.1 1.6l-.1.6-10.7-1.2.1-.6c0-.4.1-.8.4-1z"/>
+        <path class="st4"
+              d="M535.3 503.7c.6-11.4.5-27.5-2.6-36.6 0-.2-23.9 2-23.9 2l-5.6 22.9c-2 8.1-2.9 16.3-2.8 24.6l.3 34.4 4 .3 7.5-45.5c2.8-5.4 5.8-11.6 8.1-17.7l8.7 63.4 4-.2c0-.1 2.3-47.6 2.3-47.6z"/>
+        <path class="st9" d="M504.5 551.2L503.8 554.4 500.9 554 501 551z"/>
+        <path class="st10"
+              d="M481.6 394.3c.7-.3 1.6 0 1.9.7 2 4 4.2 7.8 6.6 11.5 2.4 3.7 5 7.2 7.8 10.5s5.8 6.4 9.1 9.1c1.6 1.4 3.3 2.7 5 3.9.4.3.9.6 1.3.9l1.3.9c.9.6 1.8 1.1 2.7 1.7.3.2.5.4.8.6.2.2.4.5.6.7.3.5.6 1.1.7 1.8.3 1.3.1 2.7-.7 4-.8 1.3-2 2.1-3.3 2.3-.7.1-1.4.1-2.1 0-.3-.1-.7-.2-1-.3l-.9-.6c-.9-.7-1.8-1.5-2.7-2.3l-1.3-1.2c-.4-.4-.9-.8-1.3-1.2-1.7-1.6-3.4-3.4-4.9-5.1-3.1-3.5-6-7.3-8.5-11.2-2.5-3.9-4.7-8-6.6-12.2-1.9-4.2-3.6-8.4-5.1-12.7-.5-.7-.1-1.5.6-1.8z"/>
+        <path class="st2" d="M500.2 434.6l9.4 7.3c2.8 2.2 6.8 1.9 9-.9s1.8-7.2-1.1-9.4l-9.4-7.3-7.9 10.3z"/>
+        <path class="st2"
+              d="M521.8 428.5c-9-.1-16 7.9-14.8 16.8l1.8 23.7c10 3.6 17.5 1.6 23.9-2l1.1-25.2c.7-7.1-4.9-13.2-12-13.3z"/>
+        <path class="st1"
+              d="M531.8 433.5l-.2.2c1 1.4 1.7 3 2 4.7h.3c-.3-1.7-1-3.4-2.1-4.9zm-9.9 37.3v.3c2.2-.2 4.4-.8 6.6-1.7l-.1-.2c-2.1.8-4.2 1.3-6.5 1.6zm5.1-41.3c-1.6-.8-3.4-1.2-5.2-1.2h-.2c-4.3 0-8.5 1.9-11.3 5.2-1.7 1.9-2.8 4.2-3.3 6.6l.3.1c1.5-6.6 7.4-11.6 14.5-11.5 1.9 0 3.6.5 5.2 1.2v-.4zM508.6 466c-.1 0-.2.1-.3.1l.2 3 .2.1c2.2.8 4.5 1.4 6.6 1.7v-.3c-2.1-.3-4.2-.8-6.5-1.7l-.2-2.9zm-1.8-20.6l.9 12h.3l-.9-12h-.3z"/>
+        <path class="st3" d="M524 412.1s6.2 1.5 4.7 8.4c-1 4.6-4.4 7-9.2 7.8l4.5-16.2z"/>
+        <path class="st9" d="M517.5 423.7l.5 7.1c2 1.2 4 1.1 5.9-.3l-.5-7.1-5.9.3z"/>
+        <path class="st10" d="M517.6 424.6l.1 2.2c.9.5 1.9.7 3 .7h.2c1-.1 2-.5 2.7-1.2l-.1-2.1-5.9.4z"/>
+        <path class="st9"
+              d="M514.6 415.4l.4 5.3.1 1.2c.3 2.9 2.7 5.1 5.6 5.1.3 0 .6 0 .9-.1.1 0 .2-.1.3-.1h.1c.4-.2.8-.4 1.1-.8.7-.8 1.1-1.6 1.5-2.5.3-.7.6-1.5.8-2.2.2-.9.4-1.8.2-2.8l-.4-4.6-9-.7-1.6 2.2z"/>
+        <path class="st3"
+              d="M523.9 414s-10.3.6-8.2 9.7c0 0-3.2-6.5.1-10.9 3.6-4.8 8.5-3.2 10.2-.9 1.7 2.3 3 6.1-1.8 8.9-.1-.1 1.5-3.5-.3-6.8z"/>
+        <path class="st9"
+              d="M523.7 419.5c.1 1.2 1.1 2.1 2.3 2 1.2-.1 2.1-1.1 2-2.3-.1-1.2-1.1-2.1-2.3-2-1.2.1-2.1 1.1-2 2.3z"/>
+        <g><path class="st3" d="M503.8 450.8l-7.4-8c4.5-4.2 6.9-9.8 6.9-15.9h10.9c0 9.1-3.8 17.8-10.4 23.9z"/>
+            <path class="st4"
+                  d="M514.2 427h-10.9c0-12-9.7-21.7-21.7-21.7-2.6 0-5.1.4-7.5 1.3l-3.8-10.2c3.6-1.3 7.4-2 11.3-2 18-.1 32.6 14.6 32.6 32.6z"/>
+            <path class="st2"
+                  d="M481.6 459.6c-18 0-32.6-14.6-32.6-32.6 0-13.6 8.6-25.9 21.4-30.6l3.8 10.2c-8.5 3.1-14.2 11.3-14.2 20.4 0 12 9.7 21.7 21.7 21.7 5.5 0 10.8-2.1 14.8-5.8l7.4 8c-6.1 5.6-14 8.7-22.3 8.7z"/></g>
+        <g><path class="st9" d="M471.1 455.3c0-.8.5-1.5 1.3-1.5 4.4-.5 8.8-1.1 13.1-2.1 4.3-.9 8.5-2.1 12.6-3.5 4.1-1.5 8-3.2 11.8-5.2 1.9-1 3.7-2.1 5.5-3.3.4-.3.9-.6 1.3-.9l1.3-.9c.8-.6 1.7-1.2 2.5-1.9.3-.2.6-.4.8-.5l.9-.3c.6-.1 1.3-.1 1.9 0 1.3.2 2.6.9 3.5 2.1.9 1.2 1.2 2.6 1 3.9-.1.7-.4 1.3-.8 1.9-.2.3-.4.6-.7.8-.3.3-.6.5-.9.7-1 .6-2.1 1.2-3.1 1.7l-1.6.8c-.5.3-1.1.5-1.6.8-2.1 1-4.3 2-6.5 2.8-4.4 1.7-9 3-13.5 3.9-4.6.9-9.2 1.5-13.7 1.9-4.6.3-9.1.4-13.7.3-.8 0-1.4-.7-1.4-1.5z"/>
+            <path class="st2"
+                  d="M515.5 452.5l10.1-6.2c3.1-1.9 4.3-5.7 2.4-8.8-1.9-3.1-6.1-4.2-9.1-2.3l-10.1 6.2 6.7 11.1z"/>
+            <path class="st1"
+                  d="M529.1 439.4c-.1-.7-.4-1.4-.8-2-.9-1.5-2.5-2.7-4.3-3.1-.3-.1-.6-.1-.9-.2v.3c2 .3 3.9 1.4 4.9 3.2 1.4 2.3 1.1 5-.5 7l.2.1c1.3-1.5 1.8-3.4 1.4-5.3zm-3.3 7.1s.1 0 .1-.1l-.3-.1-3 1.8.2.2 3-1.8zm-4.2 2.6l-.2-.2-2.9 1.7.2.2 2.9-1.7zm-4.4 2.6l-.2-.2-1.5.9-5.2-8.5-.2.1 5.3 8.8 1.8-1.1zm.2-15.9l-7.4 4.5.1.2 7.4-4.5-.1-.2z"/></g></g>
+    <g><path class="st10" d="M234.4 464c0-.8-.5-1.5-1.3-1.6-2.3-.3-4.6-.6-6.9-1-2.3-.4-4.5-.8-6.7-1.3s-4.3-1.2-6.2-2c-1.9-.8-3.7-1.9-5.3-3.1-3.2-2.5-5.7-6-8-9.7-.3-.5-.6-.9-.9-1.4l-.8-1.4c-.6-1-1.1-2-1.7-3-1.1-2-2.2-4-3.2-6.1-1.4-2.6-4.7-3.5-7.2-2s-3.3 4.8-1.7 7.3c1.4 1.9 2.7 3.9 4.1 5.8.7 1 1.4 1.9 2.2 2.9l1.1 1.4c.4.5.8.9 1.1 1.4 1.6 1.9 3.2 3.7 5 5.5 1.8 1.8 3.9 3.4 6.1 4.8 2.3 1.3 4.7 2.3 7.2 3 2.5.7 4.9 1.1 7.3 1.3 2.4.2 4.8.4 7.1.4 2.4.1 4.7.1 7 .1 1 0 1.7-.6 1.7-1.3z"/>
+        <path class="st3" d="M190.5 450.4l-6.3-10c-1.9-3-1.3-7 1.8-8.9 3-1.9 7.3-1.1 9.2 2l6.3 10-11 6.9z"/>
+        <path class="st9" d="M181.4 505.2L189.7 554.4 192.6 553.9 193.4 504.8z"/>
+        <path class="st4" d="M194.2 504.7l-13.6.5c-3.7-9-6.9-28.9-3.1-38.1l15.2 3.4 1.5 34.2z"/>
+        <circle transform="rotate(-16.739 184.847 470.406)" class="st4" cx="184.8" cy="470.4" r="7.9"/>
+        <g class="st11"><path class="st4" d="M184.8 470.4L184.8 470.4 184.8 470.4 184.8 470.3z"/></g>
+        <path class="st9" d="M165.9 503.2L161.1 553.4 164.1 553.6 177.6 505.8z"/>
+        <path class="st4"
+              d="M180.4 462.7c-3.2-1-6.5.2-8.5 2.7-.1.2-.3.4-.4.6-5.7 8.3-7.5 27.6-6.3 37l13.2 3 7.3-33.4c1.3-4.2-1.1-8.6-5.3-9.9z"/>
+        <path class="st2"
+              d="M180.4 497.1l-1.9 8.9-2.2-.5v.3l2.4.5 2-9.1-.3-.1zm-11.9-25.8c-1.3 3.5-2.4 7.8-3.1 12.8v.3h.3c.6-4.6 1.7-9.1 3.1-12.9l-.3-.2zm-3.9 23.7h.3c0-2.2.2-4.5.4-6.8h-.3c-.3 2.3-.4 4.6-.4 6.8zm.6 8c-.2-1.3-.3-2.8-.3-4.4h-.3c.1 1.6.2 3.1.3 4.4v.2l8 1.8.1-.2-7.8-1.8zm18.6-21.8l-1.7 7.9h.3l1.7-7.9h-.3z"/>
+        <path class="st3"
+              d="M170.4 556.6c-.2-.4-.6-.6-1-.7-.4-.1-.8-.1-1.2-.2-1-.2-2-.6-2.8-1.2-.4-.3-.8-.7-1.3-.9l-2.9-.3c-.4.4-.8.9-1 1.5-.2.5-.2 1.1-.3 1.6l-.1.6 10.7 1.2.1-.6c.1-.3 0-.7-.2-1zM199.5 555.1c-.3-.3-.7-.4-1.2-.4-.4 0-.8.1-1.3.1-1 .1-2.1 0-3-.4l-1.5-.6-2.9.5c-.3.5-.5 1.1-.5 1.7 0 .5 0 1.1.1 1.6l.1.6 10.7-1.6-.1-.6c0-.3-.1-.6-.4-.9zM182 428.8c9 .1 15.8 8.2 14.4 17.1l-3.6 24c-6.5 2.3-15.6 1.5-23.1-.7v-27.4c-.5-7.1 5.2-13.1 12.3-13z"/>
+        <path class="st1"
+              d="M169.4 457.4v10.4h.3v-10.4h-.3zm12.6-28.8h-.1c-.4 0-.8 0-1.2.1v.3c.4 0 .8-.1 1.3-.1 2.1 0 4 .5 5.8 1.2l.1-.2c-1.8-.9-3.8-1.3-5.9-1.3zm11.3 5.3c-.8-.9-1.7-1.8-2.7-2.5l-.2.2c3.7 2.7 6.1 7.1 6.1 11.9 0 .8-.1 1.6-.2 2.4l-.8 5.3.3.1.8-5.3c.7-4.4-.5-8.8-3.3-12.1zm-.6 36c-5.9 2.1-13.9 1.6-20.9-.1v.3c4 1 8.1 1.5 11.8 1.5 3.5 0 6.6-.5 9.2-1.4l.1-.1 2.1-13.8h-.3l-2 13.6zm-16.9-39.5l-.1-.3c-1.1.6-2.1 1.4-2.9 2.3-2.4 2.5-3.6 5.9-3.3 9.3v6.4h.3v-6.4c-.4-4.7 2.1-9 6-11.3z"/>
+        <g><path class="st9" d="M186.2 424.7l-.4 7.3c-2.1 1.1-4 1-5.9-.4l.4-7.3 5.9.4z"/>
+            <path class="st10" d="M186.1 426.9v.8c-.9.5-2 .7-3.1.7h-.2c-1-.1-1.9-.5-2.6-1.2l.1-2.1 5.8 1.8z"/>
+            <path class="st9"
+                  d="M189.3 416.4l-.5 5.2-.1 1.2c-.3 2.9-2.8 5.1-5.7 5-.3 0-.6-.1-.9-.1-.1 0-.2-.1-.3-.1h-.1c-.4-.2-.8-.5-1.1-.8-.6-.8-1-1.6-1.4-2.5-.3-.8-.6-1.5-.8-2.3-.2-.9-.3-1.8-.2-2.8l.2-3.6 9.3-1.5 1.6 2.3z"/>
+            <path class="st3"
+                  d="M189 424.6s0-3.1-.1-4.6c-.1-1.4-.4-2.8-1.5-2.6-2.1.4-2.9-1.4-2.9-1.4-.6 0-1.2.1-1.9.3-3.1.8-3.6 0-4-.5-.8 2.4-.5 5.5-.5 5.8 0 .1.1.3.1.4.2.8.5 1.5.8 2.3.3.7.6 1.4 1 2v.5c-2.2-.4-4.9-2.8-5.6-4.7-2.3-7.2 1.6-11.5 7.1-12.6 4.8-.9 7.4 3.5 8.4 7.5.8 2.3-.3 7-.9 7.6z"/>
+            <path class="st9"
+                  d="M180.2 420.3c-.1 1.2-1.1 2.1-2.3 2-1.2-.1-2.1-1.1-2-2.3.1-1.2 1.1-2.1 2.3-2 1.2.1 2 1.1 2 2.3z"/></g>
+        <g><path transform="rotate(-180 274.437 454.01)" class="st2" d="M269 446.1H279.8V462H269z"/>
+            <path transform="rotate(-180 260.511 447.387)" class="st2"
+                  d="M255.1 432.8H265.9V461.90000000000003H255.1z"/>
+            <path transform="rotate(-180 246.585 443.424)" class="st4" d="M241.2 424.9H252V461.9H241.2z"/>
+            <path transform="rotate(-180 232.659 439.712)" class="st4" d="M227.2 417.5H238V461.9H227.2z"/>
+            <path transform="rotate(-180 218.732 441.217)" class="st4"
+                  d="M213.3 420.5H224.10000000000002V461.9H213.3z"/>
+            <path transform="rotate(-180 204.806 443.424)" class="st2"
+                  d="M199.4 424.9H210.20000000000002V461.9H199.4z"/>
+            <path transform="rotate(-180 190.88 447.387)" class="st4" d="M185.5 432.8H196.3V461.90000000000003H185.5z"/>
+            <g><path transform="rotate(-180 232.659 462.663)" class="st3" d="M183.1 461.9H282.3V463.4H183.1z"/></g></g>
+        <g><path class="st9" d="M227.5 461.9c-.1-.8-.7-1.4-1.5-1.4h-6.9c-2.3-.1-4.6-.2-6.8-.4s-4.4-.6-6.4-1.1c-2-.6-3.9-1.3-5.7-2.4-3.5-2.1-6.5-5.1-9.3-8.5-.4-.4-.7-.8-1.1-1.3l-1-1.3c-.7-.9-1.4-1.8-2-2.7-1.4-1.8-2.7-3.7-4-5.6-1.7-2.3-5.1-2.8-7.4-.9-2.3 1.8-2.6 5.3-.6 7.5 1.6 1.7 3.2 3.4 4.9 5.2.8.9 1.7 1.7 2.5 2.6l1.3 1.3c.4.4.9.8 1.3 1.2 1.8 1.7 3.7 3.3 5.8 4.8 2.1 1.5 4.3 2.8 6.7 3.9 2.4 1 5 1.7 7.5 2 2.5.3 5 .4 7.4.3 2.4-.1 4.8-.3 7.1-.6s4.7-.6 7-.9c.7-.3 1.2-1 1.2-1.7z"/>
+            <path class="st3" d="M181.9 454.2l-7.7-9c-2.3-2.7-2.2-6.7.5-9.1 2.7-2.3 7.1-2.1 9.4.7l7.7 9-9.9 8.4z"/>
+            <path class="st1"
+                  d="M179.6 434.3c-1.2-.1-2.3.1-3.4.6l.1.2c2.6-1.2 5.9-.6 7.8 1.7l.7.8.2-.2-.7-.8c-1.1-1.3-2.8-2.2-4.7-2.3zm12.2 11.6l-8.1 6.8.2.2 8.3-7-4.4-5.2-.2.2 4.2 5zm-18-9.1c-1.8 2.1-1.9 5.2-.4 7.7l.2-.1c-1.4-2.3-1.4-5.3.3-7.4l-.1-.2zm7.4 17l.2-.2-3.7-4.4-.2.2 3.7 4.4z"/></g></g>
+    <g><path class="st3" d="M630.9 587.7H74.2c-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9H631c1.6 0 2.9 1.3 2.9 2.9-.1 1.6-1.4 2.9-3 2.9z"/></g>
+    <g><path transform="rotate(-40.957 194.403 297.627)" class="st2" d="M179.5 288.7H209.2V306.4H179.5z"/>
+        <path transform="rotate(-40.957 148.955 337.083)" class="st4" d="M103.6 323.8H194.2V350.40000000000003H103.6z"/>
+        <path class="st4"
+              d="M294.2 300.4c28.1-24.4 31.2-67.2 6.7-95.3-24.4-28.1-67.2-31.2-95.3-6.7-25.9 22.5-30.5 60.4-12.1 88.2 1.6 2.4 3.4 4.8 5.4 7.1 2 2.3 4.1 4.4 6.2 6.3 25 22.1 63.3 22.9 89.1.4zm-76.9-88.6c20.7-18 52.3-15.8 70.3 5s15.8 52.3-5 70.3-52.3 15.8-70.3-5-15.8-52.3 5-70.3z"/>
+        <g style="opacity:.5"><path class="st2" d="M212.3 282.1c-18-20.8-15.8-52.3 5-70.3 20.7-18 52.3-15.8 70.3 5s15.8 52.3-5 70.3c-20.7 17.9-52.3 15.7-70.3-5z"/></g>
+        <g><path class="st1" d="M263.6 217c.2-.4.4-.7.8-1 1-.8 2.5-.5 3.2.5l20.8 28.3c.8 1 .5 2.5-.5 3.2-1 .8-2.5.5-3.2-.5l-20.8-28.3c-.5-.6-.6-1.5-.3-2.2zM252.5 225.2c.2-.4.4-.7.8-1 1-.8 2.5-.5 3.2.5l20.8 28.3c.8 1 .5 2.5-.5 3.2-1 .8-2.5.5-3.2-.5l-20.8-28.3c-.5-.6-.6-1.5-.3-2.2z"/></g></g>
+    <g><path class="st3" d="M410 551.8l-12.9 6.5c-.2-.4-.3-.9-.2-1.4.1-.6.5-1 .9-1.3.5-.3 1-.6 1.5-.8 1.2-.7 2.2-1.6 3-2.8.4-.6.7-1.2 1.2-1.8l3.6-1.7c.7.3 1.4.8 1.9 1.4.4.5.7 1.2 1 1.9zM422.6 556.4l-14.4 1.9c-.1-.5 0-1 .2-1.4.3-.5.8-.8 1.3-1 .5-.2 1.1-.2 1.7-.3 1.4-.2 2.6-.8 3.7-1.6.6-.4 1.1-.9 1.7-1.3l3.9-.4c.6.5 1.1 1.2 1.3 2 .4.7.5 1.4.6 2.1zM416.5 508.6L416.5 508.6 416.5 508.6z"/>
+        <g class="st11"><path class="st3" d="M414.6 478.3L414.6 478.3 414.6 478.3 414.6 478.3z"/></g>
+        <path class="st3" d="M416.5 508.6L416.5 508.6 416.5 508.6zM416.5 508.6L416.5 508.6 416.5 508.6z"/>
+        <path class="st2"
+              d="M384.1 510.1l18.8 40.3 4.7-1.9-12-37.6 9.7-15.3 11 57.2 5.1-.3.1-73.2c.1-.7-30.9-2.6-30.9-2.6l-6.8 30.4c-.1 1.1 0 2 .3 3zm32.4-1.5z"/>
+        <g class="st11"><path class="st3" d="M416.5 508.6L416.5 508.6 416.5 508.6z"/></g>
+        <path class="st10"
+              d="M352.8 484.7c1.5-1.5 3-2.8 4.5-4.2.4-.3.7-.7 1.1-1 .4-.4.7-.7 1.1-1l1-1.1.5-.5.5-.5c2.7-2.9 5.1-6 7.3-9.3 1-1.7 2.1-3.3 3.1-5l.7-1.3c.1-.2.2-.4.4-.6l.3-.7 1.3-2.6c.1-.2.2-.4.3-.7l.3-.7.6-1.3.6-1.3.3-.7.2-.3.1-.3 1.1-2.7c.4-.9.7-1.8 1.1-2.8.4-1.2 1.4-2 2.6-2.4 1.2-.4 2.6-.4 3.9.2 1.3.6 2.3 1.6 2.8 2.7.5 1.2.5 2.5-.1 3.7-.5.9-1 1.8-1.6 2.8l-1.6 2.7-.2.3-.2.3-.4.7-.9 1.3-.9 1.3-.4.7c-.1.2-.3.4-.5.7l-1.8 2.6-.5.6c-.2.2-.3.4-.5.6l-1 1.3c-1.3 1.7-2.7 3.3-4.1 4.9-2.9 3.1-6 6.1-9.2 8.7l-.6.5-.6.5-1.3.9c-.4.3-.8.6-1.3.9-.4.3-.8.6-1.3.9-1.7 1.2-3.4 2.3-5 3.4-.5.4-1.3.3-1.9-.3-.3-.6-.3-1.4.2-1.9z"/>
+        <path class="st4" d="M383.8 465.5l5.6-12.2c1.7-3.7.4-8-3.3-9.7-3.7-1.7-8.4-.1-10 3.6l-5.6 12.2 13.3 6.1z"/>
+        <g><path class="st4" d="M389.8 435.7c-7.7 1.9-12.2 9.9-9.6 17.5l11.9 36.1c13.9 1.7 20.3-2.7 29.7-9.7l-12.4-33.5c-3-7.9-11.4-12.4-19.6-10.4z"/></g>
+        <g><path class="st1" d="M403.3 438.2c1.4 1 2.7 2.2 3.8 3.7l.3-.3c-1.1-1.4-2.4-2.7-3.8-3.7l-.3.3zm16 43.1l.2.3c.8-.6 1.5-1.1 2.3-1.7l.2-.2-3.2-8.7-.4.1 3.1 8.5c-.6.5-1.4 1.1-2.2 1.7zm-27.6 8.3h.3c2.7.3 5.1.5 7.3.4l-.1-.4c-2.2.1-4.5 0-7.2-.3l-7-21.2-.4.1 7.1 21.4zm20.6-3.7c-1 .5-1.9 1-3 1.4l.2.4c1.1-.5 2.1-1 3.1-1.5l-.3-.3zm2.1-26l.4-.2-3-8-.4.2 3 8zm-32.6-.2l.4-.1-2.1-6.5c-2.3-6.8 1.2-14.1 7.6-16.8.3-.1.6-.3 1-.4l-.2-.4-.9.3c-6.8 2.8-10.3 10.4-7.9 17.4l2.1 6.5z"/></g>
+        <g><path class="st4" d="M353.2 491.4c8.2 7.9 20.6 10.6 31.7 6l-11.3-26.9-20.4 20.9z"/>
+            <path class="st3"
+                  d="M373.6 470.4l29.2-1.4c-.2-3.3-.9-6.7-2.3-9.9-2.7-6.4-7.4-11.3-13.1-14.4l-13.8 25.7zM362.2 443.5l11.3 26.9 13.9-25.7c-7.4-4-16.7-4.8-25.2-1.2z"/>
+            <path class="st3" d="M373.6 470.4l11.3 26.9c11.6-4.9 18.4-16.4 17.8-28.3l-29.1 1.4z"/>
+            <path class="st4" d="M346.7 481.8c1.6 3.7 3.8 7 6.6 9.6l20.4-21-29-3.4c-.7 4.9-.1 9.9 2 14.8z"/></g>
+        <g><path class="st9" d="M371.3 467.3c.5-.6 1.3-.7 1.9-.3 1.8 1.2 3.7 2.4 5.6 3.4.9.5 1.9 1 2.9 1.4 1 .4 1.9.8 2.9 1.1 1 .3 1.9.5 2.8.5h1.2c.4 0 .8-.1 1.1-.2.3-.1.6-.3.9-.4.3-.2.5-.4.8-.6.2-.2.5-.5.7-.8.2-.3.4-.6.6-1 .4-.7.7-1.6 1-2.5.3-.9.5-1.8.7-2.8.4-2 .6-4 .7-6.1.1-2.1.2-4.2.2-6.4 0-2.1-.1-4.3-.2-6.5 0-1.4.5-2.6 1.5-3.6.9-.9 2.2-1.5 3.7-1.5 1.4 0 2.7.6 3.7 1.6.9 1 1.4 2.3 1.3 3.7-.2 2.3-.4 4.6-.8 6.8-.3 2.3-.7 4.6-1.1 6.9-.5 2.3-1 4.6-1.8 7-.4 1.2-.8 2.3-1.4 3.5-.6 1.2-1.2 2.3-2 3.4-.4.6-.9 1.1-1.4 1.6-.5.5-1.1 1-1.7 1.4-.6.4-1.3.8-2.1 1.1-.7.3-1.5.5-2.2.6-.7.1-1.5.1-2.2.1h-.6l-.5-.1c-.3-.1-.7-.1-1-.2-1.3-.3-2.5-.7-3.6-1.2s-2.2-1.1-3.2-1.7c-1-.6-2-1.3-2.9-2-1.9-1.4-3.6-2.8-5.3-4.3-.6-.5-.7-1.3-.2-1.9z"/>
+            <path class="st4" d="M406.8 461.8l.4-13.4c.1-4.1-2.8-7.6-6.8-7.7-4.1-.1-7.7 3.2-7.9 7.2l-.4 13.4 14.7.5z"/>
+            <path class="st1"
+                  d="M392.2 449.3h.4V448c0-.5.1-.9.2-1.4l-.4-.1c-.1.5-.2 1-.2 1.5v1.3zm11.3 12.8l3.7.1.1-4.1-.4-.1-.1 3.8-3.3-.1v.4zm3.7-11.4l-.2 4.9h.4l.2-5-.4.1zm-12.7-7.9l.4.1c1.5-1.4 3.4-2.2 5.5-2.2 1.7.1 3.3.7 4.4 1.8l.2-.3c-1.3-1.2-2.9-1.8-4.6-1.9-1.1 0-2.2.2-3.3.6-1 .5-1.9 1.1-2.6 1.9zm-2.6 15.2l.4.1.1-3.4h-.4l-.1 3.3zm4.5 3.9l3.4.1v-.4l-3.3-.1-.1.4z"/></g>
+        <g><path class="st9" d="M383 434.5l4.8 8.4c2.5.2 4.3-.9 5.5-3.1l-4.8-8.4-5.5 3.1z"/>
+            <path class="st10"
+                  d="M383 434.5l2.2 3.8c.5-.1.9-.2 1.4-.4.1-.1.3-.1.4-.2 1.5-.9 2.5-2.4 2.8-4l-1.3-2.3-5.5 3.1z"/>
+            <path class="st9"
+                  d="M377.6 430.8l2.1 3.5.9 1.4c1.2 1.9 3.7 2.7 5.7 1.7.7-.3 1.3-.8 1.7-1.3.1-.1.2-.3.3-.4.1-.1.1-.2.2-.3.5-.9.8-1.8.7-2.9-.1-.9-.2-1.8-.3-2.8-.1-.6-.2-1.1-.3-1.7-.5-2.5-2.3-4.6-4.7-5.3-2.1-.6-4-.1-5.6 1.6-1.7 1.8-2 4.4-.7 6.5z"/>
+            <path class="st3"
+                  d="M385.7 429.1h-.8s0-3.2-.8-3.9c-1.2-1-4.5.3-5.9 1.4-.4.4-.7.7-.7 1.3.1 1.2.5 3.5 1.7 5.5 0 0-5-5.2-4.5-9.4.3-2.8.8-4.8 4.3-4.8 5.5 0 9.6 2.7 11.2 8.4l-3.5.2-1 1.3z"/>
+            <g><path class="st9" d="M385.9 429.5c.6 1 2 1.3 3.1.7s1.6-2 1-3c-.6-1-2-1.3-3.1-.7-1.2.6-1.6 2-1 3z"/></g></g></g>
+    <g><path class="st5" d="M305 499.7H375.1V558.3H305z"/>
+        <path class="st13" d="M281.2 499.7H305V558.3H281.2z"/>
+        <path class="st13"
+              d="M305 499.7L295.6 517.6 269.4 517.6 281.2 499.7zM386 519.6L316 524.1 305 499.7 375.1 504.2z"/>
+        <path class="st5"
+              d="M386 519.6L316 519.6 305 499.7 375.1 499.7zM305 499.7L299.7 519.7 269.4 517.6 295.6 517.6z"/></g>
+    <g><path class="st0" d="M38.9 241.8c3.5-18.6 10.8-36.5 20.7-52.7 5-8.1 10.7-15.8 17.1-22.9 3.2-3.6 6.5-7 10-10.3 3.5-3.3 7.1-6.4 10.8-9.4 15-11.9 32.3-20.9 50.6-26.7 9.2-2.9 18.6-4.9 28.1-6.1 2.4-.3 4.8-.5 7.1-.8l3.6-.3c1.2-.1 2.4-.1 3.6-.2 4.8-.2 9.6-.2 14.4 0 4.8.2 9.6.7 14.3 1.3 4.8.6 9.5 1.5 14.2 2.5 2.3.5 4.7 1.1 7 1.7l3.5 1c1.2.3 2.3.7 3.4 1.1.6.2 1.1.4 1.7.5l1.7.6c1.1.4 2.3.8 3.4 1.2 1.1.4 2.2.8 3.4 1.3l3.3 1.4c.6.2 1.1.5 1.7.7l1.6.7 3.3 1.5 3.2 1.6 1.6.8 1.6.8 3.2 1.7 3.1 1.7 1.6.9 1.5.9 3.1 1.8c4.1 2.4 8.1 4.9 12.1 7.5 4 2.6 7.9 5.2 11.9 7.9 7.8 5.3 15.6 10.7 23.5 15.9 3.9 2.6 7.9 5.1 11.9 7.6 4 2.4 8.1 4.8 12.2 7.1 2 1.2 4.1 2.2 6.2 3.3 1 .6 2.1 1 3.2 1.6 1.1.5 2.1 1.1 3.2 1.5 2.1 1 4.3 2 6.5 2.8 1.1.4 2.2.9 3.3 1.3l3.3 1.2 3.3 1.2c1.1.4 2.2.8 3.4 1.1l3.4 1c.6.2 1.1.3 1.7.5l1.7.4c1.1.3 2.3.6 3.4.8l3.5.7c.6.1 1.2.2 1.7.3l1.7.3 3.5.5c-9.4-.8-18.8-2.7-27.8-5.6-9-2.9-17.8-6.7-26.3-11-4.3-2.1-8.4-4.4-12.5-6.8-4.1-2.4-8.2-4.8-12.2-7.3s-8-5.1-12-7.6l-11.9-7.7c-4-2.6-7.9-5.1-11.9-7.6s-8-4.9-12.1-7.3l-3.1-1.7-1.5-.9-1.5-.8-3.1-1.7-3.1-1.6-1.6-.8-1.6-.8-3.2-1.5-3.2-1.4-1.6-.7c-.5-.2-1.1-.4-1.6-.7-17.2-7.2-35.7-11.2-54.3-11.9-18.6-.8-37.4 1.5-55.2 6.9-4.5 1.3-8.9 2.9-13.2 4.6-4.3 1.7-8.6 3.7-12.7 5.8-8.3 4.2-16.2 9.2-23.7 14.8-7.4 5.7-14.4 11.9-20.8 18.8-6.4 6.8-12.2 14.2-17.4 22-10.6 15.9-18.3 33.3-22.9 51.7z"/></g>
+    <g><path class="st0" d="M658 370.2c6.5 13.9 10.3 29.1 11.5 44.5 1.1 15.4-.4 31.1-4.6 46.1-4.2 14.9-11.2 29.1-20.3 41.6-9.1 12.5-20.3 23.5-33.2 31.9 11.9-9.7 22.3-21 30.7-33.6 8.4-12.6 14.9-26.4 19-41 4.1-14.5 5.9-29.7 5.3-44.9-.4-15.1-3.3-30.2-8.4-44.6z"/></g>
+    <g><path class="st1" d="M639.8 422.2c.4 9.5-.9 19.2-3.6 28.3-1.4 4.6-3.1 9.1-5.2 13.4-2.1 4.3-4.6 8.5-7.3 12.4-2.8 3.9-5.9 7.6-9.2 11.1-3.4 3.4-7 6.6-10.9 9.4-7.7 5.7-16.4 10.1-25.5 12.9 8.8-3.5 17.1-8.3 24.6-14.1 3.7-2.9 7.2-6.1 10.5-9.5 3.3-3.4 6.3-7 9-10.9 2.7-3.8 5.1-7.9 7.3-12.1 2.1-4.2 3.9-8.6 5.4-13.1 2.9-8.8 4.5-18.2 4.9-27.8z"/></g></svg>

+ 3 - 0
common/src/assets/svgs/message.svg

@@ -0,0 +1,3 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
+    <path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/>
+</svg>

+ 12 - 0
common/src/assets/svgs/warning.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g id="页面-1" stroke-width="1" fill-rule="evenodd">
+        <g id="icon" transform="translate(-191.000000, -66.000000)" fill-rule="nonzero">
+            <g id="健康监测" transform="translate(191.000000, 66.000000)">
+                <path d="M14.5454545,0 L1.45454546,0 C0.651222186,0 0,0.651222186 0,1.45454546 L0,7.27272729 L0.610181829,7.27272729 L0.841454556,6.73018182 C1.07051796,6.19792201 1.5965672,5.85505831 2.17600002,5.86036363 C2.75854547,5.86690908 3.27418182,6.22036363 3.49090909,6.76145454 L4.30181819,8.79345456 L6.58254545,1.95200001 C6.54545911,1.65544427 6.6935726,1.36638989 6.95594678,1.22327982 C7.21832096,1.08016975 7.54153582,1.11214154 7.7707872,1.30388227 C8.00003858,1.495623 8.08865036,1.80809379 7.99418181,2.09163637 L9.03272726,10.4007273 L9.85018182,7.78327274 C9.94545455,7.47927275 10.2269091,7.27272729 10.5447273,7.27272729 L11.8378182,7.27272729 C12.0894545,6.84000002 12.5534545,6.54545457 13.0909091,6.54545454 C13.8942323,6.54545454 14.5454545,7.19667672 14.5454545,8 C14.5454545,8.80332328 13.8942323,9.45454546 13.0909091,9.45454546 C12.5733609,9.45309513 12.0958126,9.17593418 11.8378182,8.72727271 L11.0792727,8.72727271 L9.4210909,14.0349091 C9.31982648,14.3622727 9.00359601,14.5743782 8.66228958,14.5438606 C8.32098315,14.513343 8.04739868,14.2484996 8.00581819,13.9083636 L6.9490909,5.45381819 L5.0538182,11.1389091 C4.95709094,11.4283637 4.69018182,11.6269091 4.38472728,11.6363636 C4.08218181,11.6298182 3.80145456,11.4625454 3.68800002,11.1789091 L2.14036363,7.30109092 L1.76000001,8.28581818 C1.6458182,8.55345456 1.3818182,8.72727271 1.09090909,8.72727271 L0,8.72727271 L0,14.5454545 C0,15.3487778 0.651222186,16 1.45454546,16 L14.5454545,16 C15.3487778,16 16,15.3487778 16,14.5454545 L16,1.45454546 C16,0.651222186 15.3487778,0 14.5454545,0 Z"
+                      id="路径"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 84 - 0
common/src/axios/config.ts

@@ -0,0 +1,84 @@
+import { AxiosResponse, InternalAxiosRequestConfig } from './types'
+import { ElMessage } from 'element-plus'
+import qs from 'qs'
+import { SUCCESS_CODE, EXPIRE_CODE, TRANSFORM_REQUEST_DATA } from '../constants'
+// @ts-ignore
+import { API_URL_MESSAGE_TIP } from '@/utils/messageApi'
+import { API_URL_MESSAGE_TIP_COMMON } from '../api/messageApi'
+
+// import { useUserStoreWithOut } from '@common/src/store/modules/user'
+import { objToFormData } from '../utils'
+import { useUserStoreWithOut } from '../store/modules/user'
+import { usePermissionStoreWithOut } from '../store/modules/permission'
+
+const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
+  if (
+    config.method === 'post' &&
+    config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
+  ) {
+    config.data = qs.stringify(config.data)
+  } else if (
+    TRANSFORM_REQUEST_DATA &&
+    config.method === 'post' &&
+    config.headers['Content-Type'] === 'multipart/form-data' &&
+    !(config.data instanceof FormData)
+  ) {
+    config.data = objToFormData(config.data)
+  }
+  if (config.method === 'get' && config.params) {
+    let url = config.url as string
+    url += '?'
+    const keys = Object.keys(config.params)
+    for (const key of keys) {
+      if (config.params[key] !== void 0 && config.params[key] !== null) {
+        url += `${key}=${encodeURIComponent(config.params[key])}&`
+      }
+    }
+    url = url.substring(0, url.length - 1)
+    config.params = {}
+    config.url = url
+  }
+  return config
+}
+
+const defaultResponseInterceptors = (response: AxiosResponse) => {
+  if (response.data.code === EXPIRE_CODE) {
+    ElMessage.warning('权限认证已过期,即将离开本页面')
+    setTimeout(() => {
+      const userStore = useUserStoreWithOut()
+      if (sessionStorage.getItem('flag')) {
+        const usePermissionStore = usePermissionStoreWithOut()
+        userStore.setToken('')
+        userStore.setUserInfo(undefined)
+        userStore.setRoleRouters([])
+        usePermissionStore.resetAddRouters()
+        window.location.href = response?.data?.attachments?.loginUrl
+      } else {
+        userStore.reset()
+      }
+    }, 3000)
+    return
+  }
+  if (response.data.success) {
+    const url: any = response?.config?.url?.split('?')[0]
+    // 统一处理需要展示提示信息的接口
+    if (API_URL_MESSAGE_TIP.includes(url) || API_URL_MESSAGE_TIP_COMMON.includes(url)) {
+      ElMessage.success(response?.data?.message)
+    }
+  }
+  // console.log(response)
+  if (response?.config?.responseType === 'blob') {
+    // 如果是文件流,直接过
+    // 当前业务处理形式  如果是文件流,直接过
+    return {
+      data: response.data,
+      name: response?.headers?.['content-disposition'] || '导出文件.xlsx'
+    }
+  } else if (response.data.success || response.data.code === SUCCESS_CODE) {
+    return response.data
+  } else {
+    ElMessage.error(response?.data?.message || 'Error Response')
+  }
+}
+
+export { defaultResponseInterceptors, defaultRequestInterceptors }

+ 42 - 0
common/src/axios/index.ts

@@ -0,0 +1,42 @@
+import service from './service'
+import { CONTENT_TYPE } from '../constants'
+import { useUserStoreWithOut } from '../store/modules/user'
+
+const request = (option: AxiosConfig) => {
+  const { url, method, params, data, headers, responseType } = option
+
+  const userStore = useUserStoreWithOut()
+  return service.request({
+    url: url,
+    method,
+    params,
+    data: data,
+    responseType: responseType,
+    headers: {
+      'Content-Type': CONTENT_TYPE,
+      [userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
+      ...headers
+    }
+  })
+}
+
+export default {
+  get: <T = any>(option: AxiosConfig) => {
+    return request({ method: 'get', ...option }) as Promise<IResponse<T>>
+  },
+  post: <T = any>(option: AxiosConfig) => {
+    return request({ method: 'post', ...option }) as Promise<IResponse<T>>
+  },
+  delete: <T = any>(option: AxiosConfig) => {
+    return request({ method: 'delete', ...option }) as Promise<IResponse<T>>
+  },
+  put: <T = any>(option: AxiosConfig) => {
+    return request({ method: 'put', ...option }) as Promise<IResponse<T>>
+  },
+  cancelRequest: (url: string | string[]) => {
+    return service.cancelRequest(url)
+  },
+  cancelAllRequest: () => {
+    return service.cancelAllRequest()
+  }
+}

+ 73 - 0
common/src/axios/service.ts

@@ -0,0 +1,73 @@
+import axios from 'axios'
+import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
+
+import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
+import { ElMessage } from 'element-plus'
+import { REQUEST_TIMEOUT } from '../constants'
+
+export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
+
+const abortControllerMap: Map<string, AbortController> = new Map()
+const axiosInstance: AxiosInstance = axios.create({
+  timeout: REQUEST_TIMEOUT,
+  baseURL: PATH_URL
+})
+
+axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
+  const controller = new AbortController()
+  const url = res.url || ''
+  res.signal = controller.signal
+  abortControllerMap.set(url, controller)
+  return res
+})
+
+axiosInstance.interceptors.response.use(
+  (res: AxiosResponse) => {
+    const url = res.config.url || ''
+    abortControllerMap.delete(url)
+    // 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
+    return res
+  },
+  (error) => {
+    console.log('err: ' + error) // for debug
+    ElMessage.error(error?.response?.data?.message || '请求失败')
+    return Promise.reject(error)
+  }
+)
+// 拦截器
+axiosInstance.interceptors.request.use(defaultRequestInterceptors)
+axiosInstance.interceptors.response.use(defaultResponseInterceptors)
+
+const service = {
+  request: (config: RequestConfig) => {
+    return new Promise((resolve, reject) => {
+      if (config.interceptors?.requestInterceptors) {
+        config = config.interceptors.requestInterceptors(config as any)
+      }
+
+      axiosInstance
+        .request(config)
+        .then((res) => {
+          resolve(res)
+        })
+        .catch((err: any) => {
+          reject(err)
+        })
+    })
+  },
+  cancelRequest: (url: string | string[]) => {
+    const urlList = Array.isArray(url) ? url : [url]
+    for (const _url of urlList) {
+      abortControllerMap.get(_url)?.abort()
+      abortControllerMap.delete(_url)
+    }
+  },
+  cancelAllRequest() {
+    for (const [_, controller] of abortControllerMap) {
+      controller.abort()
+    }
+    abortControllerMap.clear()
+  }
+}
+
+export default service

+ 31 - 0
common/src/axios/types/index.ts

@@ -0,0 +1,31 @@
+import type {
+    InternalAxiosRequestConfig,
+    AxiosResponse,
+    AxiosRequestConfig,
+    AxiosInstance,
+    AxiosRequestHeaders,
+    AxiosError
+} from 'axios'
+
+interface RequestInterceptors<T> {
+    // 请求拦截
+    requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
+    requestInterceptorsCatch?: (err: any) => any
+    // 响应拦截
+    responseInterceptors?: (config: T) => T
+    responseInterceptorsCatch?: (err: any) => any
+}
+
+interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
+    interceptors?: RequestInterceptors<T>
+}
+
+export {
+    AxiosResponse,
+    RequestInterceptors,
+    RequestConfig,
+    AxiosInstance,
+    InternalAxiosRequestConfig,
+    AxiosRequestHeaders,
+    AxiosError
+}

+ 46 - 0
common/src/business-components/BackButton.vue

@@ -0,0 +1,46 @@
+<!-- 
+  * 底部操作按钮
+  * 可配合 组件一起使用
+  * 也可以单独使用
+-->
+<template>
+  <div class="button-bottom">
+    <el-button @click="back"> {{ text }}</el-button>
+  </div>
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router'
+
+const { go } = useRouter()
+
+defineProps({
+  text: {
+    type: String,
+    default: '返回'
+  }
+})
+const emits = defineEmits(['handle-back'])
+
+const back = () => {
+  go(-1)
+  emits('handle-back')
+}
+</script>
+
+<style lang="less">
+.button-bottom {
+  width: 100%;
+  background: #ffffff;
+  border-top: 1px solid var(--el-border-color);
+  // position: absolute;
+  bottom: 0;
+  left: 0;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  z-index: 10;
+  padding-top: 16px;
+  margin-top: 16px;
+}
+</style>

+ 52 - 0
common/src/business-components/ButtonBottom.vue

@@ -0,0 +1,52 @@
+<!-- 
+  * 底部操作按钮
+  * 可配合 组件一起使用
+  * 也可以单独使用
+-->
+<template>
+  <div class="button-bottom">
+    <el-button v-if="!hideSave" type="primary" @click="$emit('submit')"> {{ saveText }}</el-button>
+    <el-button v-if="!hideCancel" @click="$emit('close')"> {{ cancelText }}</el-button>
+  </div>
+</template>
+
+<script setup>
+import { defineProps, defineEmits } from 'vue'
+
+defineProps({
+  hideSave: {
+    type: Boolean,
+    default: false
+  },
+  saveText: {
+    type: String,
+    default: '保存'
+  },
+  hideCancel: {
+    type: Boolean,
+    default: false
+  },
+  cancelText: {
+    type: String,
+    default: '取消'
+  }
+})
+defineEmits(['submit', 'close'])
+</script>
+
+<style lang="less">
+.button-bottom {
+  width: 100%;
+  height: 64px;
+  background: #ffffff;
+  border-top: 1px solid var(--el-border-color);
+  // position: absolute;
+  bottom: 0;
+  left: 0;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  z-index: 10;
+  // padding: 0 var(--el-card-padding);
+}
+</style>

+ 34 - 0
common/src/business-components/DeptSelect.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+/*
+ * 表单组件 选择部门树
+ * */
+import { getDeptTree } from '../api'
+import { ref } from 'vue'
+
+const model = defineModel()
+const data = ref([])
+
+const getDeptTreeApi = async () => {
+  const res = await getDeptTree()
+  data.value = res.data
+}
+getDeptTreeApi()
+</script>
+
+<template>
+  <el-tree-select
+    v-model="model"
+    :data="data"
+    :render-after-expand="false"
+    check-strictly
+    check-on-click-node
+    clearable
+    :props="{
+      label: 'name'
+    }"
+    node-key="id"
+    v-bind="$attrs"
+  />
+</template>
+
+<style scoped lang="less"></style>

+ 44 - 0
common/src/business-components/DistrictSelect.vue

@@ -0,0 +1,44 @@
+<script setup lang="ts">
+import { getDistrictTree } from '../api'
+import { ref } from 'vue'
+
+const props = defineProps({
+  // 最顶级节点是否disabled
+  topDisabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const model = defineModel()
+const data = ref([])
+
+const getDistrictTreeApi = async () => {
+  const res = await getDistrictTree()
+  data.value = res.data
+  if (props.topDisabled) {
+    res.data[0].disabled = true
+  }
+}
+getDistrictTreeApi()
+</script>
+
+<template>
+  <el-tree-select
+    v-model="model"
+    :data="data"
+    :render-after-expand="false"
+    :default-expanded-keys="['3600']"
+    check-strictly
+    check-on-click-node
+    clearable
+    :props="{
+      label: 'name'
+    }"
+    node-key="id"
+    v-bind="$attrs"
+    class="min-w-180px w-100%"
+  />
+</template>
+
+<style scoped lang="less"></style>

+ 3 - 0
common/src/business-components/DynamicForm/index.ts

@@ -0,0 +1,3 @@
+import DynamicForm from './src/DynamicForm.vue'
+
+export default DynamicForm

+ 146 - 0
common/src/business-components/DynamicForm/src/DynamicFormDemo.vue

@@ -0,0 +1,146 @@
+<!-- *
+ * 请讲此组件放进你的项目中进行使用
+ * 此处是处理与业务相关的入口处,是你需要进行按照业务进行修改的地方
+ * 如 获取数据 提交表单等等
+ *
+ * 如果需要使用anchor锚点功能 请在component中 useAnchor:true
+ * 并根据页面样式高度 自定义在样式中设置锚点滚动盒子的高度
+ * 示例:
+ :deep(.anchor-scroll-box) {
+  height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - 210px) !important;
+}
+-->
+
+<script setup lang="tsx">
+import { ref, unref, watch, shallowRef } from 'vue'
+import FormCommon from './templates/FormCommon.vue'
+import FormDeptAuth from './templates/FormDeptAuth.vue'
+import ButtonBottom from '../../ButtonBottom.vue'
+import {
+  getFormConfig,
+  getFormData
+} from './api/dynamic-form'
+// 以下是业务接口 按需进行修改 【获取详情 新增 修改】
+// @ts-ignore
+import { findById, updateForm, addForm } from '@/api/xxx'
+
+// 如果需要集成按钮部分
+import { useRouter } from 'vue-router'
+
+const { go } = useRouter()
+
+const props = defineProps({
+  // 查询详情用的参数
+  findId: {
+    type: String,
+    default: ''
+  },
+  // 表单是否可编辑模式
+  formMode: {
+    type: String,
+    default: ''
+  }
+})
+
+// 详情
+const detail = ref()
+const getDeatil = async () => {
+  const { data } = await findById({ reportNo: props.findId })
+  detail.value = data
+
+  formType.value = formTypeEnum[data.pageTemplate]
+
+  getFormConfigMethod()
+}
+watch(
+  () => props.findId,
+  () => {
+    getDeatil()
+  },
+  {
+    immediate: true
+  }
+)
+
+//**** 表单相关
+const formType = shallowRef(FormCommon)
+// 多种表单类型
+const formTypeEnum = {
+  custom: FormCommon,
+  auth: FormDeptAuth
+}
+// 动态表单数据 信息获取
+const templateForm = ref([])
+const templateFormData = ref()
+const getFormConfigMethod = async () => {
+  // 1. 获取表单数据
+  const { data: formData } = await getFormData({
+    tableName: detail.value.tableName,
+    key: 'id',
+    value: detail.value.dataId
+  })
+  templateFormData.value = formData
+  // 2. 获取表单配置
+  const { data } = await getFormConfig({ leadServiceTableId: detail.value.dataType })
+  templateForm.value = data
+}
+const formRef = ref()
+
+// 表单提交操作
+const submit = async () => {
+  const write = unref(formRef)
+  const data = await write?.submit()
+  const { tableName, xxx } = unref(detail)
+
+  let res
+  if (templateFormData.value) {
+    res = await updateData(tableName, 'task_no', props.findId, data)
+  } else res = await addData(tableName, data)
+  const { success } = res
+
+  // showBtn为true 在本组件内处理状态
+  // 否则将状态返回给调用者
+  // 请自行选择
+  if (props.showBtn) {
+    if (success) close()
+  } else {
+    return success
+  }
+}
+
+const close = () => {
+  go(-1)
+}
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <component
+    :is="formType"
+    ref="formRef"
+    :templateForm="templateForm"
+    :templateFormData="templateFormData"
+    :formMode="formMode"
+  >
+    <ButtonBottom
+      v-if="formMode !== 'look'"
+      :hide-save="formMode === 'look'"
+      :cancel-text="'取消'"
+      @close="close"
+      @submit="submit"
+    />
+  </component>
+</template>
+
+<style lang="less" scoped>
+.dynamic-form {
+  position: relative;
+
+  &:has(.button-bottom) {
+    padding-bottom: 60px;
+  }
+}
+</style>

+ 15 - 0
common/src/business-components/DynamicForm/src/api/dynamic-form.ts

@@ -0,0 +1,15 @@
+import request from '../../../../axios'
+// 根据ID和部门编号查询表单配置
+export const getFormConfig = (params) => {
+  return request.get({
+    url: `/api/dynamicform/dynamicServiceTable/findFormById`,
+    params
+  })
+}
+// 查询表数据
+export const getFormData = (params) => {
+  return request.get({
+    url: `/api/dynamicform/customForm/findById`,
+    params
+  })
+}

+ 42 - 0
common/src/business-components/DynamicForm/src/components/Autocomplete.vue

@@ -0,0 +1,42 @@
+<template>
+  <span v-if="isLook">{{ select_look || '-' }}</span>
+  <el-autocomplete
+    v-else
+    v-model="select"
+    :value-key="field.label"
+    :fetch-suggestions="querySearch"
+    :disabled="disabled"
+  />
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook, select, select_look } = useOptions(props, emits)
+
+const querySearch = (queryString, cb) => {
+  const results = queryString ? options.filter((i) => i[field.label] === queryString) : options
+  // 调用 callback 返回建议列表的数据
+  cb(results)
+}
+</script>

+ 54 - 0
common/src/business-components/DynamicForm/src/components/Cascader.vue

@@ -0,0 +1,54 @@
+<template>
+  <span v-if="isLook">{{ select_look || '-' }}</span>
+  <el-cascader
+    v-else
+    ref="tree"
+    v-model="select"
+    :options="options"
+    :props="treeProps"
+    :disabled="disabled"
+    :show-all-levels="false"
+    filterable
+    clearable
+  >
+    <template #default="{ node, data }">
+      <span>{{ data[field.label] }}</span>
+      <!-- 需要拼装子级的处理 -->
+      <span v-if="node.isLeaf && data.remark"> ({{ data.remark }}) </span>
+    </template>
+  </el-cascader>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook, select_look, select } = useOptions(props, emits)
+
+const treeProps = {
+  emitPath: false,
+  multiple: props.formItem.isMultiple,
+  value: field.value,
+  label: field.label
+}
+</script>

+ 39 - 0
common/src/business-components/DynamicForm/src/components/Checkbox.vue

@@ -0,0 +1,39 @@
+<template>
+  <span v-if="isLook">
+    {{ select_look || '-' }}
+  </span>
+  <el-checkbox-group v-else v-model="select" :disabled="disabled">
+    <el-checkbox
+      v-for="item in options"
+      :key="item[field.value]"
+      :value="item[field.value]"
+      :label="item[field.label]"
+    />
+  </el-checkbox-group>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook, select_look, select } = useOptions(props, emits)
+</script>

+ 163 - 0
common/src/business-components/DynamicForm/src/components/CheckboxOther.vue

@@ -0,0 +1,163 @@
+<template>
+  <span v-if="isLook">{{ select_look || '-' }}</span>
+  <el-checkbox-group v-model="select" :disabled="disabled" v-else>
+    <template #default>
+      <el-checkbox
+        v-for="item in options"
+        :key="item[field.value]"
+        :value="item[field.value]"
+        :label="item[field.label]"
+      />
+      <el-input
+        v-model="other"
+        v-if="showInput"
+        :disabled="disabled"
+        placeholder="请输入其他内容"
+        @change="inputChange"
+      />
+    </template>
+  </el-checkbox-group>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+import { ref, computed, watch } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: [Array<string>],
+    default: () => []
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook } = useOptions(props, emits)
+
+// 其它 的功能
+const other = ref('')
+// 其它 这个选项的内容 主要用来筛选那个属于 其它 项
+const otherItem = ref()
+const showInput = ref(false)
+// 记录用于表单的值
+const formVal = ref<string[]>([])
+const findOther = () => {
+  // 如果包含其它项 记下来 用于之后的匹配
+  const item = options.find((i) => i[field.label] === '其它')
+  otherItem.value = item
+}
+
+watch(
+  () => options,
+  (val) => {
+    if (val?.length) {
+      findOther()
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const select = computed({
+  get() {
+    return props.modelValue
+  },
+  set(val) {
+    // 显示输入框
+    showInput.value = val.includes(otherItem.value?.[field.value])
+    // 当其他项 被取消 则删除other字段
+    if (!showInput.value && hasOtherField(val).has) {
+      removeOtherField(val)
+    }
+
+    // 如果选择了其它 并且输入了内容 则拼装
+    formVal.value =
+      showInput.value && other.value && !hasOtherField(val).has
+        ? [...(val as Array<string>), `other:${other.value}`]
+        : val
+
+    emits('update:modelValue', formVal.value)
+  }
+})
+
+// 详情状态下的显示字段
+const select_look = ref('')
+watch(
+  () => select.value,
+  (val) => {
+    if (val) {
+      // 详情的字段拼接
+      if (isLook) {
+        const other_look = val.find((i) => i.includes('other:'))
+        select_look.value = (other_look ? val.filter((i) => !i.includes('other:')) : val)
+          .map((i) => {
+            return i === otherItem.value[field.value] && other_look
+              ? `${otherItem.value[field.label]}:${other_look.split(':')[1]}`
+              : options.find((j) => j[field.value] === i)[field.label]
+          })
+          .join(',')
+      }
+
+      formVal.value = val
+      if (val.includes(otherItem.value?.[field.value])) {
+        showInput.value = true
+
+        const otherText = val.find((i) => i.includes('other:'))
+        if (otherText) {
+          other.value = otherText.split(':')[1]
+        }
+      }
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const inputChange = (val) => {
+  /**
+   * 当最后勾选其他 然后再输入时 输入的值无法被绑定的
+   * 所以需要再输入完成后 收到绑定一次
+   */
+
+  //  如果已经有other字段 移除
+  if (hasOtherField(formVal.value).has) {
+    removeOtherField()
+  }
+
+  if (val) {
+    formVal.value = [...formVal.value, `other:${other.value}`]
+
+    emits('update:modelValue', formVal.value)
+  }
+}
+
+const hasOtherField = (val) => {
+  if (val.length && val.find((i) => i.includes('other:'))) {
+    const index = val.findIndex((i) => i.includes('other:'))
+    return {
+      index,
+      has: true
+    }
+  }
+  return {
+    has: false
+  }
+}
+// 移除other:部分
+const removeOtherField = (val: undefined | Array<string> = undefined) => {
+  ;(val || formVal.value).splice(hasOtherField(val || formVal.value).index, 1)
+}
+</script>

+ 340 - 0
common/src/business-components/DynamicForm/src/components/ChildForm.vue

@@ -0,0 +1,340 @@
+<template>
+  <div class="template-child-form w-100%">
+    <el-form
+      ref="form-child"
+      :model="form"
+      :label-width="labelWidth"
+      :label-position="labelPosition"
+      :disabled="disabled || mode === 'look'"
+    >
+      <div class="row-box" v-for="(i, index) in form.arr" :key="index">
+        <el-row>
+          <el-col
+            v-for="item in formItems"
+            :key="item.prop"
+            :span="['7', '16', '17', '18'].includes(item.inputType) ? 24 : colLen"
+          >
+            <el-form-item
+              :label="item.label ? `${item.label}:` : ''"
+              :prop="`arr.${index}.${item.prop}`"
+              :rules="rules[item.prop]"
+            >
+              <component
+                :is="item.type"
+                :ref="item.type"
+                v-model="i[item.prop]"
+                :form-item="item"
+                :mode="mode"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </div>
+    </el-form>
+    <div
+      @click="handleAdd"
+      v-if="mode !== 'look' && !disabled"
+      class="add-button flex justify-center items-center cursor-pointer h-32px m-t-16px"
+    >
+      <el-icon class="m-r-4px">
+        <Plus />
+      </el-icon>
+      新增
+    </div>
+  </div>
+</template>
+<script>
+import { ElMessage } from 'element-plus'
+import Input from './Input.vue'
+import InputNumber from './InputNumber.vue'
+import Select from './Select.vue'
+import Date from './Date.vue'
+import Cascader from './Cascader.vue'
+import Autocomplete from './Autocomplete.vue'
+import FileUpload from './FileUpload/FileUpload.vue'
+import Radio from './Radio.vue'
+import Checkbox from './Checkbox.vue'
+import CheckboxOther from './CheckboxOther.vue'
+import FormHead2 from './FormHead2.vue'
+import Map from './Map.vue'
+import { cloneDeep } from 'lodash-es'
+import { useValidator } from '@common/src/hooks/web/useValidator'
+
+export default {
+  name: 'FormChild',
+  components: {
+    Input,
+    InputNumber,
+    Select,
+    Date,
+    Cascader,
+    FileUpload,
+    Autocomplete,
+    Radio,
+    Checkbox,
+    CheckboxOther,
+    FormHead2,
+    Map
+  },
+  props: {
+    formItem: {
+      type: Object,
+      default: () => {}
+    },
+    modelValue: {
+      type: String,
+      default: '[]'
+    },
+    mode: {
+      type: String,
+      default: 'add'
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    labelPosition: {
+      type: String,
+      default: 'left'
+    },
+    labelWidth: {
+      type: String,
+      default: '120px'
+    },
+    // 表单列数
+    col: {
+      type: Number,
+      default: 3
+    }
+  },
+  data() {
+    return {
+      valids: {},
+      form: {
+        arr: []
+      },
+      formItems: [], // 用于渲染表单的表单项数组
+      tempForm: [],
+      formType: {
+        1: 'Input',
+        2: 'Input',
+        3: 'Select',
+        4: 'Select',
+        5: 'Cascader',
+        6: 'Cascader',
+        7: 'FileUpload',
+        8: 'Date',
+        10: 'Date',
+        11: 'Autocomplete',
+        12: 'InputNumber',
+        13: 'Radio',
+        14: 'Checkbox',
+        15: 'CheckboxOther',
+        17: 'FormHead2',
+        19: 'Map',
+        20: 'Editor',
+        21: 'Cascader'
+      },
+      customForm: {}
+    }
+  },
+  computed: {
+    filedForm() {
+      return this.formItem.fieldTemplateList || []
+    },
+    // 动态生成规则
+    rules() {
+      const obj = {}
+      this.tempForm.forEach((item) => {
+        obj[item.prop] = []
+        // 判断是否是输入型表单
+        const isText = item.type === 'Input'
+        if (item.required) {
+          obj[item.prop].push({
+            required: true,
+            message: isText ? '请输入' : '请选择',
+            trigger: isText ? 'blur' : 'change'
+          })
+        }
+        // 自定义验证规则
+        if (item.valid) {
+          obj[item.prop].push({
+            validator: item.valid,
+            trigger: isText ? 'blur' : 'change'
+          })
+        }
+        // 有字数限制时验证规则
+        if (item.maxLength) {
+          obj[item.prop].push({
+            message: `最多 ${item.maxLength} 个字符`,
+            trigger: 'blur',
+            max: item.maxLength
+          })
+        }
+      })
+      return obj
+    },
+    modelValueArr() {
+      return this.modelValue ? JSON.parse(this.modelValue) : []
+    },
+    colLen() {
+      return 24 / this.col
+    }
+  },
+  watch: {
+    modelValue: {
+      handler(val) {
+        this.setForm()
+        if (this.mode === 'add') return
+        this.setParams()
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    handleAdd() {
+      this.addRow()
+    },
+    addRow() {
+      const obj = cloneDeep(this.customForm)
+      this.form.arr.push(obj)
+    },
+    setForm() {
+      // 根据表单配置生成 对应验证规则
+      const { idCard, phone, email } = useValidator()
+      const valids = [idCard(), phone(), email()]
+
+      this.form = {
+        arr: []
+      }
+      this.tempForm = this.filedForm.map((item) => {
+        return {
+          label: item.fieldDesc,
+          prop: item.fieldName,
+          index: item.orderNo, // 排序
+          type: this.formType[item.inputType],
+          inputState: item.inputType === '2' ? 'textarea' : '', // 输入框的类型
+          inputType: item.inputType,
+          required: item.isNull === '1', // 是否必填(0:否,1:是)
+          maxLength: item.limitLength,
+          fieldType: item.fieldType, // 字段类型(int,varchar等)
+          isPrimaryKey: item.isPrimaryKey, // 是否主键(0:否,1:是)
+          isMultiple: ['4', '6', '14', '15'].includes(item.inputType), // 下拉和树 有多选
+          // 字典与业务表查询时所用字段
+          contactType: item.contactType,
+          contactNo: item.contactNo,
+          formHead: item.formHead || '',
+          isAddReadonly: item.isAddReadonly === '1', // 只读
+          defaultValue: item.defaultValue,
+          valueType: item.valueType,
+          isFormShow: item.isFormShow === '1',
+          valid: (item.valueType && valids[Number(item.valueType) - 1].validator) || null
+        }
+      })
+
+      // 动态生成表单
+      this.tempForm
+        .filter((item) => item.prop)
+        .forEach((item) => {
+          // item.isMultiple || item.formType === 7 时,默认值为数组
+          this.customForm[item.prop] = item.defaultValue || item.isMultiple ? [] : ''
+        })
+      // 处理默认的表单项数
+      const obj = cloneDeep(this.customForm)
+
+      if (this.modelValueArr.length) {
+        const arr = []
+        for (let i = 0; i < this.modelValueArr.length; i++) {
+          arr.push(obj)
+        }
+        this.form.arr = arr
+      } else {
+        this.$nextTick(() => {
+          this.form.arr.push(obj)
+        })
+      }
+
+      // 排序
+      this.formItems = this.tempForm
+        .sort((a, b) => a.index - b.index)
+        .filter((item) => item.isFormShow)
+    },
+    resetForm() {
+      this.form = {
+        arr: []
+      }
+      this.formItems = []
+      this.$nextTick(() => {
+        this.$refs['form-child'] && this.$refs['form-child'].clearValidate()
+        // 文件上传 清空列表
+        this.$refs.FileUpload?.length && this.$refs.FileUpload[0].reset()
+      })
+    },
+    // 设置表单值
+    setParams() {
+      for (const m of this.modelValueArr) {
+        // 对多选的数据进行处理 按逗号 隔开
+        for (const i of this.tempForm) {
+          if (i.isMultiple) {
+            m[i.prop] = m[i.prop]?.split(',').filter((i) => i !== '') || []
+          }
+        }
+      }
+      this.form.arr = this.modelValueArr
+    },
+    // 获取表单值
+    getParams() {
+      return new Promise((resolve, reject) => {
+        this.$refs['form-child'].validate((valid) => {
+          if (valid) {
+            const form = cloneDeep(this.form)
+            for (const i1 of this.form.arr) {
+              for (const i2 in i1) {
+                const item = this.tempForm.find((item) => item.prop === i2)
+                // 该数据为输入类型且为整型时  为空时转为null
+                if (i1[i2] === '' && item?.fieldType === '1') {
+                  i1[i2] = null
+                }
+                // 数组转字符串
+                if (Array.isArray(i1[i2])) {
+                  i1[i2] = i1[i2].join(',')
+                }
+              }
+              // 移除为空的key
+              delete i1['']
+            }
+            resolve(form.arr)
+          } else {
+            ElMessage.warning('有必填项未完成')
+            reject(new Error(false))
+          }
+        })
+      })
+    }
+  }
+}
+</script>
+<style lang="less">
+.template-child-form {
+  .el-form {
+    .row-box + .row-box {
+      margin-top: 16px;
+      padding-top: 16px;
+      border-top: 1px dashed #e6e6e6;
+    }
+
+    .el-row {
+      margin-left: 0;
+    }
+
+    .el-form-item {
+      margin-bottom: 20px;
+    }
+  }
+
+  .add-button {
+    border: 1px dashed var(--el-color-primary);
+    color: var(--el-color-primary);
+  }
+}
+</style>

+ 64 - 0
common/src/business-components/DynamicForm/src/components/Date.vue

@@ -0,0 +1,64 @@
+<template>
+  <span v-if="mode === 'look'">
+    {{ time || '-' }}
+  </span>
+  <el-date-picker
+    v-else
+    v-model="time"
+    :type="dateType"
+    placeholder="请选择"
+    range-separator="至"
+    start-placeholder="开始日期"
+    end-placeholder="结束日期"
+    :value-format="valueFormat"
+    :disabled="disabled"
+  />
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Date, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  // 日期值的格式
+  dateFormat: {
+    type: String,
+    default: ''
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  }
+})
+
+const emits = defineEmits(['update:modelValue'])
+const time = computed({
+  get() {
+    return props.modelValue
+  },
+  set(val) {
+    emits('update:modelValue', val)
+  }
+})
+const dateType = computed(() => {
+  return props.formItem.inputType === 10 ? 'datetime' : 'date'
+})
+const valueFormat = computed(() => {
+  return props.dateFormat
+    ? props.dateFormat
+    : props.formItem.inputType === 10
+      ? 'YYYY-MM-DD HH:mm:ss'
+      : 'YYYY-MM-DD'
+})
+</script>

+ 94 - 0
common/src/business-components/DynamicForm/src/components/District.vue

@@ -0,0 +1,94 @@
+<template>
+  <span v-if="isLook">{{ select_look || '-' }}</span>
+  <el-cascader
+    v-else
+    ref="tree"
+    v-model="select"
+    :options="district"
+    :props="treeProps"
+    :disabled="disabled"
+    :show-all-levels="false"
+    filterable
+    clearable
+  >
+    <template #default="{ node, data }">
+      <span>{{ data[field.label] }}</span>
+      <!-- 需要拼装子级的处理 -->
+      <span v-if="node.isLeaf && data.remark"> ({{ data.remark }}) </span>
+    </template>
+  </el-cascader>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+import { getDistrictTree } from '../../../../api'
+import { findNodeNameById } from '../../../../utils/tree'
+import { ref } from 'vue'
+
+import { computed } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const district = ref([])
+const getDict = async () => {
+  const res = await getDistrictTree()
+  district.value = res.data
+}
+getDict()
+
+const emits = defineEmits(['update:modelValue'])
+
+// 双向绑定 选中项
+const select: any = computed({
+  get() {
+    return props.modelValue
+  },
+  set(val) {
+    emits('update:modelValue', val)
+  }
+})
+
+// 查看状态时 需要展示的值
+const select_look = computed(() => {
+  if (!isLook) return
+  const val = props.modelValue
+    ? props.formItem.isMultiple
+      ? props.modelValue
+      : [props.modelValue]
+    : ''
+
+  if (val) {
+    return (val as any[])
+      .map((i) => {
+        return findNodeNameById(district.value, 'id', i, 'name')
+      })
+      .join(',')
+  } else return '-'
+})
+
+const { isLook, field } = useOptions(props, emits)
+
+const treeProps = {
+  emitPath: false,
+  multiple: props.formItem.isMultiple,
+  value: 'id',
+  label: 'name'
+}
+</script>

+ 108 - 0
common/src/business-components/DynamicForm/src/components/FileUpload/FileUpload.vue

@@ -0,0 +1,108 @@
+<template>
+  <div style="display: flex">
+    <FileView :mode="mode" :file-list="fileList" @remove="handleRemove" />
+    <el-upload
+      v-if="mode !== 'look'"
+      v-bind="$attrs"
+      :file-list="fileList"
+      :action="action"
+      :headers="{ accesstoken: userStore.getToken }"
+      :on-success="handleSuccess"
+      list-type="picture-card"
+      :show-file-list="false"
+      :before-upload="beforeUpload"
+      :on-exceed="handleExceed"
+    >
+      <el-icon>
+        <Plus />
+      </el-icon>
+    </el-upload>
+  </div>
+</template>
+
+<script setup lang="ts">
+import FileView from './FileView.vue'
+import { useUserStoreWithOut } from '../../../../../store/modules/user'
+import { computed, ref, watch } from 'vue'
+import type { UploadUserFile } from 'element-plus'
+
+const userStore = useUserStoreWithOut()
+
+const action = '/openapi/api/file/upload'
+const fileList = ref<UploadUserFile[]>([])
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  // 展示模式
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  // 资源大小限制
+  maxSize: {
+    type: Number,
+    default: 20
+  }
+})
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      fileList.value = val.split(',').map((item) => {
+        return {
+          url: item
+        }
+      })
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const emits = defineEmits(['update:modelValue'])
+
+const handleRemove = (file) => {
+  console.log(file)
+  fileList.value = fileList.value.filter((item) => item.uid !== file.uid)
+}
+
+const handleSuccess = (response) => {
+  fileList.value.push({
+    url: response.data.fileUrl
+  })
+  console.log(fileList.value)
+  emits('update:modelValue', fileList.value.map((item) => item.url).join(','))
+}
+
+const fileUrls = computed(() => {
+  return fileList.value.map((item) => item.url).join(',')
+})
+
+defineExpose({
+  fileUrls
+})
+
+const beforeUpload = (file) => {
+  const isLimit = file.size / 1024 / 1024 <= props.maxSize
+  if (!isLimit) {
+    ElMessage.error('文件超出规定大小')
+  }
+  return isLimit
+}
+const handleExceed = () => {
+  ElMessage.error('文件数量超出限制!')
+}
+const reset = () => {
+  fileList.value = []
+}
+</script>
+
+<style lang="less" scoped>
+:deep(.el-upload--picture-card) {
+  --el-upload-picture-card-size: 100px;
+}
+</style>

+ 135 - 0
common/src/business-components/DynamicForm/src/components/FileUpload/FileView.vue

@@ -0,0 +1,135 @@
+<!-- 文件预览 -->
+<template>
+  <div>
+    <ul class="el-upload-list el-upload-list--picture-card">
+      <li v-for="item in fileList" :key="item.url" class="el-upload-list__item is-success">
+        <img
+          v-if="isImageByUrl(item.url)"
+          :src="item.url"
+          alt=""
+          class="el-upload-list__item-thumbnail"
+        />
+        <video
+          v-else-if="isVideoByUrl(item.url) || isAduioByUrl(item.url)"
+          :poster="isVideoByUrl(item.url) ? null : isAudioImg"
+          style="width: 100%; height: 100%"
+        >
+          <source :src="item.url" />
+          <source />
+        </video>
+        <img v-else src="./file.png" alt="" width="80%" height="80%" class="mt-16px" />
+        <span class="el-upload-list__item-actions">
+          <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(item)">
+            <el-icon
+              v-if="isImageByUrl(item.url) || isVideoByUrl(item.url) || isAduioByUrl(item.url)"
+              ><zoom-in
+            /></el-icon>
+            <el-icon v-else><Paperclip /></el-icon>
+          </span>
+          <span
+            v-if="mode === 'add'"
+            class="el-upload-list__item-delete"
+            @click="handleRemove(item)"
+          >
+            <el-icon><Delete /></el-icon>
+          </span>
+        </span>
+      </li>
+    </ul>
+    <el-dialog v-model="dialogVisible" append-to-body width="800px" destroy-on-close title="查看">
+      <img v-if="isImageByUrl(elsrc)" :src="elsrc" width="100%" alt />
+      <video
+        v-if="isVideoByUrl(elsrc)"
+        :src="elsrc"
+        style="width: 100%; height: 500px"
+        controls="controls"
+        controlslist="nodownload"
+        width="100%"
+        height="50%"
+      ></video>
+      <audio
+        v-if="isAduioByUrl(elsrc)"
+        :src="elsrc"
+        controls="controls"
+        controlslist="nodownload"
+        style="width: 100%"
+      ></audio>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { isVideoByUrl, isImageByUrl, isAduioByUrl, download } from './utils.js'
+import { ElDialog } from 'element-plus'
+
+export default {
+  name: 'FileView',
+  components: { ElDialog },
+  props: {
+    fileList: {
+      type: Array,
+      default: () => []
+    },
+    mode: {
+      type: String,
+      default: 'view'
+    }
+  },
+  data() {
+    return {
+      elsrc: '',
+      isVideoByUrl,
+      isImageByUrl,
+      isAduioByUrl,
+      dialogVisible: false
+    }
+  },
+
+  computed: {
+    isAudioImg() {
+      return require('@/assets/img/AUDIO.png')
+    }
+  },
+  // watch: {
+  //   fileList: {
+  //     immediate: true,
+  //     deep: true,
+  //     handler (n) {
+  //       console.log(n)
+  //     }
+  //   }
+  // },
+  methods: {
+    handlePictureCardPreview(file) {
+      this.elsrc = file.url
+      const canOpen =
+        isVideoByUrl(this.elsrc) || isImageByUrl(this.elsrc) || isAduioByUrl(this.elsrc)
+      if (canOpen) {
+        this.dialogVisible = true
+      } else {
+        download(file.url, file.name || file.url.split('/').slice(-1))
+      }
+    },
+    handleRemove(file) {
+      this.$emit('remove', file, this.fileList)
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.el-upload-list {
+  .el-upload-list__item {
+    position: relative;
+    text-align: center;
+  }
+
+  .file-icon {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    color: var(--el-color-primary);
+    font-size: 30px;
+  }
+}
+</style>

BIN
common/src/business-components/DynamicForm/src/components/FileUpload/file.png


+ 58 - 0
common/src/business-components/DynamicForm/src/components/FileUpload/utils.js

@@ -0,0 +1,58 @@
+const FILE_URL = import.meta.env.VITE_BASE_PATH
+
+export const imageSuffixes = ['.jpg', '.png', '.gif', '.jpeg']
+export const videoSuffixes = ['.avi', '.mp4', '.mpeg', '.wmv', '.mov']
+export const audioSuffixes = ['.mp3', '.wma', '.avi', '.mdi', '.rmvb', '.ogg']
+
+export const FILETYPE = {
+    IMAGE: 'IMAGE',
+    VIDEO: 'VIDEO',
+    AUDIO: 'AUDIO',
+    OTHER: 'OTHER'
+}
+
+export function isImageByUrl(src) {
+    return getFileTypeByUrl(src) === FILETYPE.IMAGE
+}
+
+export function isVideoByUrl(src) {
+    return getFileTypeByUrl(src) === FILETYPE.VIDEO
+}
+
+export function isAduioByUrl(src) {
+    return getFileTypeByUrl(src) === FILETYPE.AUDIO
+}
+
+export function getFullUrl(src) {
+    const rx = /^https?:\/\//i
+    return rx.test(src) ? src : FILE_URL + src
+}
+
+export function getFileTypeByUrl(src) {
+    src = src.toLowerCase()
+    const reg = /\.[^.]+$/
+    const matches = reg.exec(src)
+    if (matches) {
+        const suffix = matches[0]
+        if (imageSuffixes.includes(suffix)) return FILETYPE.IMAGE
+        if (videoSuffixes.includes(suffix)) return FILETYPE.VIDEO
+        if (audioSuffixes.includes(suffix)) return FILETYPE.AUDIO
+    }
+    return FILETYPE.OTHER
+}
+
+export function download(url, fileName) {
+    const xhr = new XMLHttpRequest()
+    xhr.open('get', url)
+    xhr.responseType = 'blob'
+    xhr.send()
+    xhr.onload = function (sues) {
+        const data = xhr.response
+        const imgurl = URL.createObjectURL(data)
+        let a = document.createElement('a')
+        a.href = imgurl
+        a.download = fileName || ''
+        a.click()
+        a = null
+    }
+}

+ 17 - 0
common/src/business-components/DynamicForm/src/components/FormHead1.vue

@@ -0,0 +1,17 @@
+<template>
+  <div
+    class="form-head form-head1 w-100% text-base border-0 border-b-1 border-#e6e6e6 border-solid pb-9px font-semibold"
+    :id="props.formItem.formHead"
+  >
+    {{ props.formItem.formHead }}
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  formItem: {
+    type: Object,
+    default: () => {}
+  }
+})
+</script>

+ 22 - 0
common/src/business-components/DynamicForm/src/components/FormHead2.vue

@@ -0,0 +1,22 @@
+<template>
+  <div
+    class="form-head form-head2 w-100% text-sm font-semibold border-0 border-l-4 border-solid pl-8px form-head-1"
+  >
+    {{ props.formItem.formHead }}
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  formItem: {
+    type: Object,
+    default: () => {}
+  }
+})
+</script>
+
+<style>
+.form-head-1 {
+  border-left-color: var(--el-color-primary);
+}
+</style>

+ 46 - 0
common/src/business-components/DynamicForm/src/components/Input.vue

@@ -0,0 +1,46 @@
+<template>
+  <span v-if="mode === 'look'">{{ text || props.formItem.defaultValue || '-' }}</span>
+  <el-input
+    v-else
+    v-model="text"
+    :type="formItem.inputState"
+    :disabled="disabled"
+    placeholder="请输入"
+  />
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const emits = defineEmits(['update:modelValue'])
+const props = defineProps({
+  modelValue: {
+    type: [String, Number],
+    default: ''
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const text = computed({
+  get() {
+    return props.modelValue
+  },
+  set(val) {
+    emits(
+      'update:modelValue',
+      props.formItem.fieldType === 1 && !isNaN(Number(val)) && val ? Number(val) : val
+    )
+  }
+})
+</script>

+ 37 - 0
common/src/business-components/DynamicForm/src/components/InputNumber.vue

@@ -0,0 +1,37 @@
+<template>
+  <span v-if="mode === 'look'">{{ text || '-' }}</span>
+  <el-input-number v-else v-model="text" :min="0" :disabled="disabled" />
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const emits = defineEmits(['update:modelValue'])
+const props = defineProps({
+  modelValue: {
+    type: [String, Number],
+    default: 1
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const text = computed({
+  get() {
+    return Number(props.modelValue)
+  },
+  set(val) {
+    emits('update:modelValue', Number(val))
+  }
+})
+</script>

+ 361 - 0
common/src/business-components/DynamicForm/src/components/Map.vue

@@ -0,0 +1,361 @@
+<template>
+  <span v-if="props.mode === 'look'">
+    {{ text || '-' }}
+  </span>
+  <el-input
+    v-else
+    v-model="text"
+    :type="formItem?.inputState"
+    :disabled="disabled"
+    readonly
+    placeholder="点击右侧图标选择地址"
+    @change="change"
+  >
+    <template #suffix>
+      <el-icon class="el-input__icon cursor-pointer" @click="handleClick">
+        <location />
+      </el-icon>
+    </template>
+  </el-input>
+
+  <Dialog
+    v-model="dialogVisible"
+    title="点位选择"
+    width="800px"
+    maxHeight="600px"
+    class="map-dialog"
+  >
+    <div class="map-contain">
+      <el-autocomplete
+        v-model="searchText"
+        style="width: 500px"
+        fit-input-width
+        placeholder="请输入"
+        :fetch-suggestions="querySearch"
+        :trigger-on-focus="false"
+        clearable
+        class="inline-input"
+        @select="handleSelect"
+      />
+      <div id="map"></div>
+      <div class="tip" v-if="model?.value"
+        >当前选择位置:<span>{{ model?.value }}</span></div
+      >
+    </div>
+
+    <template #footer>
+      <BaseButton type="primary" @click="handleSave">确定</BaseButton>
+      <BaseButton @click="handleClose">关闭</BaseButton>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { AutocompleteData } from 'element-plus'
+import { Dialog } from '../../../../components/Dialog'
+import AMapLoader from '@amap/amap-jsapi-loader'
+import { onMounted } from 'vue'
+
+import { ref, nextTick, watch, computed } from 'vue'
+
+const emits = defineEmits(['update:modelValue'])
+
+// let map = null;
+let AMap = null
+
+const props = defineProps({
+  modelValue: {
+    type: String,
+    default: ''
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  formData: {
+    type: Object,
+    default: () => {}
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+// 点位数据
+const mapData = computed(() => {
+  return (props.modelValue && JSON.parse(props.modelValue)) || {}
+})
+
+const text = ref('')
+// 最后绑定的数据
+const model = ref()
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      text.value = mapData.value.value
+      model.value = mapData.value
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const change = (val) => {
+  if (val) {
+    if (!model.value) {
+      model.value = {}
+    }
+    model.value.value = val
+  } else model.value = null
+
+  emits('update:modelValue', (model.value && JSON.stringify(model.value)) || '')
+}
+// 弹窗地图部分
+const dialogVisible = ref(false)
+const map = ref()
+const cityCode = window.CITY_CODE
+
+const initMap = () => {
+  console.log('initmap')
+  //@ts-ignore
+  window._AMapSecurityConfig = {
+    // securityJsCode: "2bf102f50b193a7b1e67ad563a98ad76"
+    securityJsCode: 'fb811f3c271a305025b0f95fd4817635'
+  }
+
+  AMapLoader.load({
+    // key: "147364b1245d25e8df44278a4bb1e96a", // 申请好的Web端开发者Key,首次调用 load 时必填
+    key: 'c73bccc87d2ae5998db5acf2d183128f', // 申请好的Web端开发者Key,首次调用 load 时必填
+    // version: "1.4.15", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+    version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+    plugins: ['AMap.Scale', 'AMap.PlaceSearch', 'AMap.AutoComplete', 'AMap.Geocoder'], //需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
+    AMapUI: {
+      // 是否加载 AMapUI,缺省不加载
+      // version: "1.1", // AMapUI 版本
+      // plugins: ["misc/PathSimplifier"] // 需要加载的 AMapUI ui插件
+    }
+  })
+    .then((aAMap) => {
+      AMap = aAMap
+      map.value = new AMap.Map('container', {
+        // 设置地图容器id
+        viewMode: '3D', // 是否为3D地图模式
+        zoom: 11 // 初始化地图级别
+        // center: currentLocation.value
+        //     ? currentLocation.value
+        //     : [116.397428, 39.90923] // 初始化地图中心点位置
+      })
+    })
+    .catch((error) => {
+      console.log(error, 'map error!!!!!')
+    })
+}
+
+// 图标
+const marker = ref()
+const handleClick = () => {
+  dialogVisible.value = true
+  nextTick(() => {
+    map.value = new AMap.Map('map', {
+      resizeEnable: true,
+      zoom: 14
+    })
+    map.value.on('complete', () => {
+      console.log('地图加载完成!!')
+      // 设置图标
+      marker.value = new AMap.Marker()
+
+      if (mapData.value.lnglat) {
+        setMarker(mapData.value.lnglat)
+        panTo(mapData.value.lnglat)
+      }
+    })
+    // 地图点击
+    map.value.on('click', (val) => {
+      regeoCode([val.lnglat.lng, val.lnglat.lat])
+    })
+  })
+}
+// 设置地图图标
+const setMarker = (lnglat) => {
+  // 移除上一个
+  map.value.remove(marker.value)
+  map.value.add(marker.value)
+  marker.value && marker.value.setPosition(lnglat)
+}
+// 地图移动到固定位置
+const panTo = (lnglat) => {
+  map.value.panTo(lnglat)
+}
+
+const address = ref('')
+
+// 根据经纬度反查地址信息
+const regeoCode = (lnglat: string | number[]) => {
+  AMap.plugin('AMap.Geocoder', () => {
+    const geocoder = new AMap.Geocoder({
+      city: cityCode, //默认:“全国”
+      radius: 1000 //范围,默认:500
+    })
+
+    setMarker(lnglat)
+
+    geocoder.getAddress(lnglat, (status, result) => {
+      if (status === 'complete' && result.regeocode) {
+        const {
+          formattedAddress,
+          addressComponent: { towncode }
+        } = result.regeocode
+        address.value = formattedAddress
+
+        model.value = {
+          value: address.value,
+          lnglat: lnglat,
+          district_no: towncode.slice(0, 9)
+        }
+      } else {
+        console.log('根据经纬度查询地址失败')
+      }
+    })
+  })
+}
+// ** 搜索部分
+const searchText = ref('')
+const querySearch = async (queryString: string): Promise<AutocompleteData> => {
+  if (!queryString) return []
+  const res = await searchAddress(queryString)
+  return res as AutocompleteData
+}
+// 选择搜索地址
+const handleSelect = (item) => {
+  // model.value = item
+
+  panTo(item.lnglat)
+  setMarker(item.lnglat)
+
+  regeoCode(item.lnglat)
+}
+// 地址搜索
+const searchAddress = (keywords) => {
+  return new Promise((resolve) => {
+    AMap.plugin(['AMap.PlaceSearch'], function () {
+      //构造地点查询类
+      const placeSearch = new AMap.PlaceSearch({
+        pageSize: 10, // 单页显示结果条数
+        pageIndex: 1, // 页码
+        city: '3600' // 兴趣点城市
+        // citylimit: true, //是否强制限制在设置的城市内搜索
+      })
+      //关键字查询
+      placeSearch.search(keywords, (status, result) => {
+        console.log(status, result)
+        if (status === 'complete') {
+          const data = result.poiList.pois.map((i) => {
+            return {
+              value: `${i.name} - ${i.address}`,
+              lnglat: [i.location.lng, i.location.lat]
+            }
+          })
+          resolve(data)
+        } else {
+          resolve([])
+        }
+      })
+    })
+    // AMap.plugin('AMap.AutoComplete', function () {
+    //   // 实例化Autocomplete
+    //   const autoOptions = {
+    //     city: cityCode
+    //   }
+    //   const autoComplete = new AMap.AutoComplete(autoOptions)
+    //   autoComplete.search(keywords, (status, result) => {
+    //     // 搜索成功时,result即是对应的匹配数据
+    //     if (status === 'complete') {
+    //       const data = result.tips
+    //         .filter((i) => i.id && i.location)
+    //         .map((i) => {
+    //           return {
+    //             value: `${i.name} - ${i.district}${i.address}`,
+    //             lnglat: [i.location.lng, i.location.lat]
+    //           }
+    //         })
+    //       resolve(data)
+    //     } else {
+    //       resolve([])
+    //     }
+    //   })
+    // })
+  })
+}
+
+onMounted(() => {
+  if (!map.value) {
+    initMap()
+  }
+})
+
+const handleSave = () => {
+  dialogVisible.value = false
+  text.value = address.value
+  address.value = ''
+  searchText.value = ''
+  emits('update:modelValue', JSON.stringify(model.value))
+}
+const handleClose = () => {
+  dialogVisible.value = false
+  address.value = ''
+  searchText.value = ''
+  map.value && map.value.remove(marker.value)
+}
+</script>
+
+<style lang="less" scoped>
+.map-dialog {
+  .map-contain {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    :deep(.el-input__wrapper) {
+      box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset !important;
+    }
+
+    :deep(.inline-input) {
+      position: absolute;
+      left: 16px;
+      top: 16px;
+      z-index: 99;
+      width: 300px;
+    }
+
+    .tip {
+      position: absolute;
+      bottom: 16px;
+      right: 16px;
+      background-color: #fff;
+      padding: 0 8px;
+      border-radius: 4px;
+
+      span {
+        color: var(--el-color-primary);
+      }
+    }
+  }
+
+  #map {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>
+<style>
+.map-dialog .el-scrollbar__view {
+  height: 100%;
+}
+</style>

+ 35 - 0
common/src/business-components/DynamicForm/src/components/Radio.vue

@@ -0,0 +1,35 @@
+<template>
+  <span v-if="isLook">{{ select_look || '-' }}</span>
+  <el-radio-group v-else v-model="select" :disabled="disabled">
+    <el-radio v-for="item in options" :key="item[field.value]" :value="item[field.value]">
+      {{ item[field.label] }}
+    </el-radio>
+  </el-radio-group>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook, select_look, select } = useOptions(props, emits)
+</script>

+ 112 - 0
common/src/business-components/DynamicForm/src/components/Select.vue

@@ -0,0 +1,112 @@
+<template>
+  <span v-if="isLook">{{ selectLook || '-' }}</span>
+  <el-select
+    v-else
+    v-model="select"
+    :multiple="props.formItem.isMultiple"
+    :disabled="disabled"
+    clearable
+  >
+    <el-option
+      v-for="item in selectOptions"
+      :key="item[selectField.value]"
+      :label="item[selectField.label]"
+      :value="item[selectField.value]"
+    />
+  </el-select>
+</template>
+
+<script setup lang="ts">
+import { useOptions } from '../hooks/useOptions'
+import { watch, ref, computed } from 'vue'
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Number, Array],
+    default: () => ''
+  },
+  mode: {
+    type: String,
+    default: 'add'
+  },
+  formItem: {
+    type: Object,
+    default: () => {}
+  },
+  formData: {
+    type: Object,
+    default: () => {}
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['update:modelValue'])
+
+const { options, field, isLook, select, select_look, getDynamicOptions, getUrlParams } = useOptions(
+  props,
+  emits
+)
+
+const selectLook = ref('')
+selectLook.value = select_look
+const selectField = ref(field)
+const selectOptions = ref(options)
+
+// 是否调用专门接口获取数据
+const isApi = computed(
+  () => typeof props.formItem.formHead === 'string' && props.formItem.formHead.includes('api')
+)
+// 是否包含动态参数部分 如果是 需要根据参数对应的值的变化进行动态更新
+const dynamicKey = computed(() => {
+  return isApi.value ? getUrlParams(props.formItem.formHead)[0] : ''
+})
+// 动态监听关联参数变化 进行被关联字段的重置操作
+watch(
+  () => props.formData?.[dynamicKey.value || props.formItem.prop],
+  async (newValue, oldValue) => {
+    // 1. 是api类型的
+    if (isApi.value) {
+      // 2. 如果有动态参数
+      if (dynamicKey.value) {
+        // 3. 动态参数对应的值有变化,并且不是初始化
+        if (oldValue && newValue !== oldValue) {
+          select.value = props.formItem.isMultiple ? [] : ''
+        }
+      }
+      // 拿到返回的数据
+      const res = await getDynamicOptions()
+      selectOptions.value = res.options
+      selectField.value = {
+        value: 'id',
+        label: 'name',
+        remark: 'remark'
+      }
+      // 带api的查看详情处理
+      if (isLook) {
+        selectLook.value = props.formItem.isMultiple
+          ? props.modelValue
+              .map((i) => {
+                const label = selectOptions.value.find((j) => j[selectField.value.value] === i)[
+                  selectField.value.label
+                ]
+                const remark = selectOptions.value.find((j) => j[selectField.value.value] === i)[
+                  selectField.value.remark
+                ]
+                return remark ? `${label}(${remark})` : label
+              })
+              .join(',')
+          : selectOptions.value.find((j) => j[selectField.value.value] === props.modelValue)[
+              selectField.value.label
+            ]
+      }
+    }
+  },
+  {
+    immediate: true,
+    deep: true
+  }
+)
+</script>

+ 72 - 0
common/src/business-components/DynamicForm/src/hooks/useForm.ts

@@ -0,0 +1,72 @@
+import { ITemplateFiled } from '../types'
+import { ref, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { useUserStore } from '../../../../store/modules/user'
+
+export const useOptions = (props, emits) => {
+  const tempForm = ref<ITemplateFiled[]>([])
+
+  // (0:不展示,1:单行文本,2:多行文本,3:下拉列表单选,4:下拉列表多选5:下拉树形列表单选,6:下拉树形列表多选,7:附件,8:日期选择,9:轨迹,10:日期时间选择,11.带选项的输入框,12.数字输入框 13.单选,14.多选,15.多选带其他,16.一级表单头,17.二级表单头, 18.子表单, 19.地图,20.富文本,21.下拉树形父子不关联)
+  const formType = {
+    1: 'Input',
+    2: 'Input',
+    3: 'Select',
+    4: 'Select',
+    5: 'Cascader',
+    6: 'Cascader',
+    7: 'FileUpload',
+    8: 'Date',
+    10: 'Date',
+    11: 'Autocomplete',
+    12: 'InputNumber',
+    13: 'Radio',
+    14: 'Checkbox',
+    15: 'CheckboxOther',
+    16: 'FormHead1',
+    17: 'FormHead2',
+    18: 'ChildForm',
+    19: 'Map',
+    20: 'Editor',
+    21: 'Cascader'
+  }
+
+  const form = ref()
+  const formItems = ref<ITemplateFiled[]>([])
+
+  const rules = computed(() => {
+    const obj = {}
+    tempForm.value.forEach((item) => {
+      // 判断是否是输入型表单
+      const isText = item.type === 'Input'
+      obj[item.prop] = []
+      if (item.required) {
+        obj[item.prop].push({
+          required: true,
+          message: isText ? '请输入' : '请选择',
+          trigger: isText ? 'blur' : 'change'
+        })
+      }
+      // 自定义验证规则
+      // if (item.valid) {
+      //   obj[item.prop].push({
+      //     validator: item.valid,
+      //     trigger: isText ? 'blur' : 'change'
+      //   })
+      // }
+      // 有字数限制时验证规则
+      if (item.maxLength) {
+        obj[item.prop].push({
+          message: `最多 ${item.maxLength} 个字符`,
+          trigger: 'blur',
+          max: item.maxLength
+        })
+      }
+    })
+    return obj
+  })
+
+  const route = useRoute()
+  const mode = computed(() => route.query.mode || props.formMode)
+
+  return {}
+}

+ 131 - 0
common/src/business-components/DynamicForm/src/hooks/useOptions.ts

@@ -0,0 +1,131 @@
+import { getCodeByType } from '../../../../utils'
+import { findNodeNameById } from '../../../../utils/tree'
+import { computed, ref, unref, watch } from 'vue'
+import request from '../../../../axios'
+
+export const useOptions = (props, emits) => {
+  // 字段匹配类型
+  const field = {
+    value: 'value',
+    label: 'label'
+  }
+
+  /**
+   * 异步获取选项数据
+   * 此函数用于根据表单头部信息动态获取选项数据,支持处理静态和动态URL
+   * @returns {Promise<Object>} - 返回一个Promise,解析为包含选项数据和字段配置的对象
+   */
+  const getDynamicOptions = async () => {
+    // 初始化URL,去除前后空格
+    let url = props.formItem.formHead.trim()
+    // 检查是否包含动态参数
+    if (props.formItem.formHead.indexOf('${') > -1) {
+      // 获取动态参数值
+      const dynamicKey = getUrlParams(props.formItem.formHead)[0]
+      const dynamicValue = props.formData[dynamicKey]
+      if (dynamicValue) {
+        // 替换URL中的动态参数
+        url = replaceDynamicParams(url, dynamicValue)
+      }
+    }
+
+    // 发起GET请求,获取数据
+    const res = await request.get({ url })
+
+    // 返回选项数据和字段配置
+    return {
+      options: res.data,
+      field: {
+        value: 'id',
+        label: 'name',
+        remark: 'remark'
+      }
+    }
+  }
+
+  /**
+   * 从URL字符串中提取参数。
+   *
+   * 该函数用于从URL字符串中提取所有参数部分,并将其作为数组返回。
+   * URL中的参数以${paramName}的形式表示,类似于模板字符串。
+   *
+   * @param url 要从中提取参数的URL字符串。
+   * @returns 提取到的参数名称数组。
+   */
+  const getUrlParams = (url: string): string[] => {
+    const regex = /\$\{([^}]*)\}/g
+    const matches: string[] = []
+    let match
+
+    while ((match = regex.exec(url)) !== null) {
+      matches.push(match[1])
+    }
+
+    return matches
+  }
+  /**
+   * 使用正则表达式替换URL中的动态参数
+   *
+   * @param url {string} - 输入的URL字符串,可能包含动态参数占位符
+   * @param param {string} - 用于替换动态参数占位符的实际参数值
+   * @returns {string} - 所有动态参数占位符都被替换后的URL字符串
+   */
+  const replaceDynamicParams = (url: string, param: string): string => {
+    // 定义正则表达式,用于匹配URL中的动态参数占位符
+    const regex = /\$\{[^}]*\}/g
+    // 使用正则表达式替换URL中的动态参数占位符为实际参数值
+    return url.replace(regex, param)
+  }
+
+  // 默认取字典数据
+  const options = getCodeByType(props.formItem.contactNo)
+
+  // 是详情还是其它
+  const isLook = props.mode === 'look'
+
+  // 双向绑定 选中项
+  const select = computed({
+    get() {
+      return props.modelValue
+    },
+    set(val) {
+      emits('update:modelValue', val)
+    }
+  })
+
+  // 是否调用专门接口获取数据
+  const isApi = computed(
+    () => typeof props.formItem.formHead === 'string' && props.formItem.formHead.includes('api')
+  )
+
+  // 查看状态时 需要展示的值
+  const select_look = computed(() => {
+    if (!isLook) return
+    const val = props.modelValue
+      ? props.formItem.isMultiple
+        ? props.modelValue
+        : [props.modelValue]
+      : ''
+
+    if (val) {
+      return val
+        .map((i) => {
+          // api类型的下拉 单独处理
+          return ['3', '4'].includes(props.formItem.inputType) && isApi.value
+            ? '-'
+            : options.find((j) => j[field.value] == i)[field.label]
+        })
+        .join(',')
+    } else return '-'
+  })
+
+  return {
+    options,
+    field,
+    isLook,
+    select,
+    select_look,
+    getDynamicOptions,
+    getUrlParams
+  }
+}

+ 141 - 0
common/src/business-components/DynamicForm/src/style/index.less

@@ -0,0 +1,141 @@
+.template-form-box {
+  &:has(.template-extra) {
+    padding-bottom: 60px;
+  }
+  &.is-single-col {
+    .el-input,.el-select {
+      width: 400px;
+    }
+  }
+
+  .el-row {
+    display: flex;
+    flex-wrap: wrap;
+    // margin-left: -20px;
+  }
+
+  .el-col {
+    display: flex;
+
+    .el-form-item {
+      width: 100%;
+      margin-left: 20px;
+    }
+  }
+
+  .el-autocomplete, .el-select, .el-cascader, .el-date-editor {
+    width: 100%;
+  }
+
+  .template-component {
+    width: 100%;
+  }
+
+  .el-form-item__label {
+    color: var(--el-text-color-secondary)
+  }
+  .el-form-item__content {
+    &:has(.form-head) {
+      margin-left: 0 !important;
+    }
+  }
+
+//  anchor锚点部分
+//  必须是滚动容器,才能实现锚点功能
+  &:has(.el-anchor) {
+    .anchor-scroll-box {
+      overflow-y: auto;
+      padding-left: 200px;
+      height: 500px;
+    }
+  }
+}
+
+.template-main {
+  .filter-list-wrap > div {
+    width: 100%;
+  }
+
+  .el-select, .el-cascader {
+    width: 100%;
+  }
+
+  // 第一列表格 无法 ellipsis
+  .el-table td .el-tooltip {
+    width: 100% !important;
+  }
+
+  .el-date-editor .el-range-input {
+    width: 44%;
+  }
+
+  .el-dialog {
+    width: 1000px;
+  }
+}
+
+.template-form-title {
+  font-size: 18px;
+  font-weight: 500;
+  margin-bottom: 16px;
+}
+
+// 查看详情状态下  需要边框的话
+.is-border {
+  &.template-form-box .el-row {
+    margin: 0;
+    border-top: 1px solid var(--el-border-color-lighter);
+    border-left: 1px solid var(--el-border-color-lighter);
+  }
+
+  .el-form-item {
+    margin: 0 !important;
+  }
+
+  .el-form-item__label, .el-form-item__content {
+    border: 1px solid var(--el-border-color-lighter);
+    border-left: none;
+    padding: 6px;
+
+    &:has(.form-head) {
+      border: none;
+      border-bottom: 1px solid var(--el-border-color-lighter);
+    }
+  }
+
+  .el-form-item__label {
+    width: 100%;
+    background-color: var(--el-fill-color-light);
+    margin-bottom: 0;
+    border-bottom: none;
+    border-top: none;
+    font-weight: 700;
+    color: var(--el-text-color-regular)
+  }
+  // 标签在左时的样式
+  .el-form--label-left {
+    .el-form-item__label{
+      height: 100%;
+      border-bottom: 1px solid var(--el-border-color-lighter);
+    }
+    .el-form-item__content {
+      height: 100%;
+      border-top: none;
+    }
+  }
+
+  .el-form-item__content {
+    height: calc(100% - 34px);
+    align-items: flex-start;
+
+    &:has(.form-head) {
+      height: auto;
+    }
+  }
+
+  .form-head1 {
+    border: none;
+    padding: 10px 0;
+
+  }
+}

+ 43 - 0
common/src/business-components/DynamicForm/src/templates/Anchor.vue

@@ -0,0 +1,43 @@
+<script lang="tsx">
+import { defineComponent, computed, PropType } from 'vue'
+
+export default defineComponent({
+  name: 'Anchor',
+  props: {
+    templateForm: {
+      type: Array as PropType<any[]>,
+      default: () => []
+    },
+    // affix固钉的位置高度   按需设置,过低会导致无法fixed
+    affixOffset: {
+      type: Number,
+      default: 240
+    }
+  },
+  setup(props) {
+    const anchorList: any = computed(() => {
+      // 一级表头展示在anchor里
+      return props.templateForm.filter((i) => i.inputType === '16')
+    })
+    return () => (
+      <el-affix offset={props.affixOffset} class="affix">
+        <el-anchor
+          container=".anchor-scroll-box"
+          type="underline"
+          onClick={(e) => e.preventDefault()}
+        >
+          {anchorList.value.map((item) => {
+            return <el-anchor-link href={`#${item.formHead}`}>{item.formHead}</el-anchor-link>
+          })}
+        </el-anchor>
+      </el-affix>
+    )
+  }
+})
+</script>
+
+<style scoped lang="less">
+.el-affix {
+  width: 200px !important;
+}
+</style>

+ 52 - 0
common/src/business-components/DynamicForm/src/templates/FormCommon.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="template-form-box template-form-common" :class="[colLen === 24 && 'is-single-col']">
+    <Anchor v-if="useAnchor" :templateForm="templateForm" />
+    <div class="anchor-scroll-box">
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="rules"
+        :label-width="labelWidth"
+        :label-position="labelPosition"
+      >
+        <el-row>
+          <!-- 表单头、文件上传组件 独占一行 -->
+          <el-col
+            v-for="item in formItems"
+            :key="item.prop"
+            :span="['7', '16', '17', '18'].includes(item.inputType) ? 24 : colLen"
+          >
+            <el-form-item
+              :label="item.label && item.inputType !== 18 ? `${item.label}:` : ''"
+              :prop="item.prop"
+            >
+              <component
+                :is="item.type"
+                :ref="item.prop"
+                v-model="form[item.prop]"
+                :form-item="item"
+                :form-data="form"
+                :mode="formMode"
+                :disabled="item.isAddReadonly || formMode === 'look'"
+                v-bind="getAdditionalProps(item)"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<script>
+import FormMixins from './FormMixins.vue'
+import { ElForm, ElFormItem, ElRow, ElCol } from 'element-plus'
+
+export default {
+  name: 'FormCommon',
+  mixins: [FormMixins]
+}
+</script>
+<style lang="less">
+@import '../style/index.less';
+</style>

+ 53 - 0
common/src/business-components/DynamicForm/src/templates/FormDeptAuth.vue

@@ -0,0 +1,53 @@
+<template>
+  <!-- 带部门权限的基础模板 -->
+  <div class="template-form-box template-form-common" :class="[colLen === 24 && 'is-single-col']">
+    <Anchor v-if="useAnchor" :templateForm="templateForm" />
+    <div class="anchor-scroll-box">
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="rules"
+        :label-width="labelWidth"
+        :label-position="labelPosition"
+      >
+        <el-row>
+          <!-- 表单头、文件上传组件 独占一行 -->
+          <el-col
+            v-for="item in formItems"
+            :key="item.prop"
+            :span="['7', '16', '17', '18'].includes(item.inputType) ? 24 : colLen"
+          >
+            <el-form-item
+              :label="item.label && item.inputType !== 18 ? `${item.label}:` : ''"
+              :prop="item.prop"
+            >
+              <component
+                :is="item.type"
+                :ref="item.prop"
+                v-model="form[item.prop]"
+                :form-item="item"
+                :form-data="form"
+                :mode="(noAuth(item.deptAuth) && 'look') || formMode"
+                :disabled="item.isAddReadonly || noAuth(item.deptAuth) || formMode === 'look'"
+                v-bind="getAdditionalProps(item)"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<script>
+import FormMixins from './FormMixins.vue'
+import { ElForm, ElFormItem, ElRow, ElCol } from 'element-plus'
+
+export default {
+  name: 'FormCommon',
+  mixins: [FormMixins]
+}
+</script>
+<style lang="less">
+@import '../style/index.less';
+</style>

+ 346 - 0
common/src/business-components/DynamicForm/src/templates/FormMixins.vue

@@ -0,0 +1,346 @@
+<script>
+import Input from '../components/Input.vue'
+import InputNumber from '../components/InputNumber.vue'
+import Select from '../components/Select.vue'
+import Date from '../components/Date.vue'
+import Cascader from '../components/Cascader.vue'
+import Autocomplete from '../components/Autocomplete.vue'
+import FileUpload from '../components/FileUpload/FileUpload.vue'
+import Radio from '../components/Radio.vue'
+import Checkbox from '../components/Checkbox.vue'
+import CheckboxOther from '../components/CheckboxOther.vue'
+import FormHead1 from '../components/FormHead1.vue'
+import FormHead2 from '../components/FormHead2.vue'
+import ChildForm from '../components/ChildForm.vue'
+import Map from '../components/Map.vue'
+import District from '../components/District.vue'
+
+import { ElMessage } from 'element-plus'
+import { cloneDeep } from 'lodash-es'
+import { useValidator } from '@common/src/hooks/web/useValidator'
+import { useUserStore } from '@common/src/store/modules/user'
+import Anchor from './Anchor.vue'
+
+export default {
+  name: 'FormMixins',
+  components: {
+    Input,
+    InputNumber,
+    Select,
+    Date,
+    Cascader,
+    FileUpload,
+    Autocomplete,
+    Radio,
+    Checkbox,
+    CheckboxOther,
+    FormHead1,
+    FormHead2,
+    ChildForm,
+    Map,
+    District,
+    ElMessage,
+    Anchor
+  },
+  props: {
+    templateForm: {
+      type: Array,
+      default: () => []
+    },
+    templateFormData: {
+      type: Object,
+      default: () => null
+    },
+    // 表单类型 edit add look
+    formMode: {
+      type: String,
+      default: 'add'
+    },
+    // 表单列数
+    col: {
+      type: Number,
+      default: 3
+    },
+    labelPosition: {
+      type: String,
+      default: 'left',
+      validator: (value) => {
+        return ['left', 'top'].includes(value)
+      }
+    },
+    labelWidth: {
+      type: String,
+      default: '120px'
+    },
+    // 集成锚点功能
+    useAnchor: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      tempForm: [],
+      // (0:不展示,1:单行文本,2:多行文本,3:下拉列表单选,4:下拉列表多选,
+      // 5:下拉树形列表单选,6:下拉树形列表多选,7:附件,8:日期选择,9:轨迹,10:日期时间选择,
+      // 11.带选项的输入框,12.数字输入框 13.单选,14.多选,15.多选带其他,
+      // 16.一级表单头,17.二级表单头, 18.子表单, 19.地图,20.富文本,21.下拉树形父子不关联)
+      formType: {
+        1: 'Input',
+        2: 'Input',
+        3: 'Select',
+        4: 'Select',
+        5: 'Cascader',
+        6: 'Cascader',
+        7: 'FileUpload',
+        8: 'Date',
+        10: 'Date',
+        11: 'Autocomplete',
+        12: 'InputNumber',
+        13: 'Radio',
+        14: 'Checkbox',
+        15: 'CheckboxOther',
+        16: 'FormHead1',
+        17: 'FormHead2',
+        18: 'ChildForm',
+        19: 'Map',
+        20: 'Editor',
+        21: 'Cascader',
+        // 以下是自定义 非动态部分
+        101: 'District'
+      },
+      form: {},
+      formItems: []
+    }
+  },
+  computed: {
+    // 动态生成规则
+    rules() {
+      const obj = {}
+      this.tempForm.forEach((item) => {
+        // 判断是否是输入型表单
+        const isText = item.type === 'Input'
+        obj[item.prop] = []
+        if (item.required) {
+          obj[item.prop].push({
+            required: true,
+            message: isText ? '请输入' : '请选择',
+            trigger: isText ? 'blur' : 'change'
+          })
+        }
+        // 自定义验证规则
+        if (item.valid) {
+          obj[item.prop].push({
+            validator: item.valid,
+            trigger: isText ? 'blur' : 'change'
+          })
+        }
+        // 有字数限制时验证规则
+        if (item.maxLength) {
+          obj[item.prop].push({
+            message: `最多 ${item.maxLength} 个字符`,
+            trigger: 'blur',
+            max: item.maxLength
+          })
+        }
+      })
+      return obj
+    },
+    // 用户的部门信息 用于进行 权限处理
+    deptList() {
+      return useUserStore().getUserDeptList
+    },
+    colLen() {
+      return 24 / this.col
+    }
+  },
+  watch: {
+    templateForm: {
+      handler(n) {
+        if (!n?.length) {
+          // console.log('无表单')
+          return
+        }
+        this.setForm(n)
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    // 额外属性 只有部分组件才会使用
+    getAdditionalProps(item) {
+      if (item.inputType === '18') {
+        // 子表单使用
+        return {
+          labelWidth: this.labelWidth,
+          labelPosition: this.labelPosition,
+          col: this.col
+        }
+      } else {
+        return {}
+      }
+    },
+    setForm(arr) {
+      // 根据表单配置生成 对应验证规则
+      const { idCard, phone, email } = useValidator()
+      const valids = [idCard(), phone(), email()]
+
+      this.form = {}
+      // 动态生成表单
+      this.tempForm = arr
+        .map((item) => {
+          // 拼装
+          return {
+            label: item.fieldDesc,
+            prop: item.fieldName,
+            index: item.orderNo, // 排序
+            type: this.formType[item.inputType],
+            inputState: item.inputType === '2' ? 'textarea' : '', // 输入框的类型
+            inputType: item.inputType,
+            required: item.isNull === '1', // 是否必填(0:否,1:是)
+            maxLength: item.limitLength,
+            isListShow: item.isListShow === '1', // 是否列表中展示(0:否,1:是)
+            fieldType: item.fieldType, // 字段类型(int,varchar等)
+            isPrimaryKey: item.isPrimaryKey, // 是否主键(0:否,1:是)
+            isMultiple: ['4', '6', '14', '15'].includes(item.inputType), // 下拉和树 有多选
+            isSearch: item.isSearch === '1', // 可搜索字段
+            // 字典与业务表查询时所用字段
+            contactType: item.contactType,
+            contactNo: item.contactNo,
+            formHead: item.formHead || '',
+            fieldTemplateList: item.fieldTemplateList, // 子表单数据
+            deptAuth: item.deptAuth, // 部门权限
+            isAddReadonly: item.isAddReadonly === '1', // 只读
+            defaultValue: item.defaultValue,
+            valueType: item.valueType, // 验证规则
+            isFormShow: item.isFormShow === '1' // 是否在表单中显示
+          }
+        })
+        .map((item) => {
+          /**
+           * 增加验证功能 valueType 1 身份证 2 手机号 3 邮箱
+           */
+          if (item.valueType) {
+            item.valid = valids[Number(item.valueType) - 1].validator
+          }
+          return item
+        })
+
+      // 默认值处理
+      this.tempForm
+        .filter((item) => item.prop)
+        .forEach((item) => {
+          // item.isMultiple || item.formType === 7 时,默认值为数组 || inputType=8 =10 为null()日期
+          // this.form[item.prop] = item.defaultValue || (item.isMultiple ? [] : '')
+          if (item.defaultValue) {
+            this.form[item.prop] = item.defaultValue
+          } else if (item.isMultiple) {
+            this.form[item.prop] = []
+          } else if (item.inputType === '8' || item.inputType === '10') {
+            this.form[item.prop] = null
+          } else this.form[item.prop] = ''
+        })
+      // 排序 过滤 显示的
+      this.formItems = this.tempForm
+        .sort((a, b) => a.index - b.index)
+        .filter((item) => item.isFormShow)
+      this.setParams()
+    },
+    resetForm() {
+      this.form = {}
+      this.formItems = []
+      this.$nextTick(() => {
+        this.$refs.form && this.$refs.form.clearValidate()
+        // 文件上传 清空列表
+        this.$refs.FileUpload?.length && this.$refs.FileUpload[0].reset()
+      })
+    },
+    // 设置表单值
+    async setParams() {
+      const data = this.templateFormData
+      if (!data) return
+      for (const i of this.tempForm) {
+        if (i.isMultiple && data[i.prop]) {
+          data[i.prop] =
+            (Array.isArray(data[i.prop]) ? data[i.prop] : data[i.prop]?.split(',')).filter(
+              (i) => i !== ''
+            ) || []
+        }
+      }
+      this.form = { ...data }
+    },
+    // 获取表单值
+    getParams() {
+      return new Promise((resolve, reject) => {
+        this.$refs.form.validate(async (valid) => {
+          if (valid) {
+            let form = cloneDeep(this.form)
+
+            for (const i in form) {
+              const item = this.tempForm.find((item) => item.prop === i)
+              // 有子表单时 获取并验证子表单的数据
+              if (item?.fieldTemplateList?.length) {
+                const res = await this.$refs[item.prop][0].getParams().catch((err) => {
+                  console.log(err)
+                  reject(new Error(false))
+                  // return false
+                })
+                // 拼装子表单数据
+                form = { ...form, [item.prop]: JSON.stringify(res) }
+              }
+
+              // 使用地图组件时 如果选取点位 则单独存一下 district_no 字段
+              if (item?.inputType === '19') {
+                if (form[item.prop]) {
+                  const val = JSON.parse(form[item.prop])
+                  form.district_no = val.district_no
+                } else form.district_no = ''
+              }
+
+              // 该数据为输入类型且为整型时  为空时转为null
+              if (form[i] === '' && item?.fieldType === '1') {
+                form[i] = null
+              }
+              // 数组转字符串
+              if (Array.isArray(form[i])) {
+                form[i] = form[i].join(',')
+              }
+            }
+            // 移除为空的key
+            delete form['']
+
+            resolve(form)
+          } else {
+            ElMessage.warning('有必填项未完成')
+            reject(new Error(false))
+          }
+        })
+      })
+    },
+    // 提交表单
+    async submit() {
+      const params = await this.getParams()
+      console.log(params)
+      if (!params) return
+
+      return params
+    },
+    // 查看当前表单是否有权限操作
+    noAuth(deptStringList) {
+      if (!deptStringList) return true
+      let index = 0
+      const data = String(deptStringList)
+
+      for (const i of data.split(',')) {
+        const item = this.deptList.find((j) => j.deptId == i)
+        if (item) index++
+      }
+      return !index
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+@import '../style/index.less';
+</style>

+ 33 - 0
common/src/business-components/DynamicForm/src/types/index.ts

@@ -0,0 +1,33 @@
+import { bool } from 'vue-types'
+
+// 选项的类型
+export interface IOption {
+  value: number
+  label: string
+}
+
+export interface ITemplateFiled {
+  label: string
+  prop: string
+  index: string
+  type: string
+  inputState: string
+  inputType: string
+  required: boolean
+  maxLength: string
+  isListShow: boolean
+  fieldType: string
+  isPrimaryKey: string
+  isMultiple: boolean
+  isSearch: boolean
+  // 字典与业务表查询时所用字段
+  contactType: string
+  contactNo: string
+  formHead: string
+  fieldTemplateList: string
+  deptAuth: string
+  isAddReadonly: boolean
+  defaultValue: string
+  valueType: string
+  isFormShow: boolean
+}

+ 33 - 0
common/src/business-components/HeaderFlag/Flag.vue

@@ -0,0 +1,33 @@
+<template>
+  <!-- 农业和林业人员进入流调系统是在顶部显示的标记图块 -->
+  <div class="flag-box" :class="[`box${deptType}`]" v-if="deptType === '2' || deptType === '3'">
+    <img src="./imgs/nongye-icon.png" alt="" v-if="deptType === '2'" />
+    <img src="./imgs/linye-icon.png" alt="" v-else />
+    {{ deptType === '2' ? '农' : '林' }}业流调
+  </div>
+</template>
+<script setup>
+const deptType = JSON.parse(sessionStorage.getItem('user')).userInfo.deptList[0].deptType
+</script>
+<style lang="less" scoped>
+.flag-box {
+  width: 160px;
+  height: 40px;
+  line-height: 40px;
+  font-size: 14px;
+  color: #666;
+  box-shadow: 0 0 6px var(--el-box-shadow-dark);
+  margin-right: 16px;
+  &.box2 {
+    background: url('imgs/nongye-img.png');
+  }
+  &.box3 {
+    background: url('imgs/linye-img.png');
+  }
+  img {
+    margin-right: 4px;
+    margin-left: 12px;
+    vertical-align: middle;
+  }
+}
+</style>

BIN
common/src/business-components/HeaderFlag/imgs/linye-icon.png


BIN
common/src/business-components/HeaderFlag/imgs/linye-img.png


BIN
common/src/business-components/HeaderFlag/imgs/nongye-icon.png


BIN
common/src/business-components/HeaderFlag/imgs/nongye-img.png


+ 59 - 0
common/src/business-components/ImportFile.vue

@@ -0,0 +1,59 @@
+<template>
+  <el-upload class="upload-button" action="" :http-request="upload" :show-file-list="false">
+    <el-button type="primary">{{ buttonText }}</el-button>
+  </el-upload>
+</template>
+<script lang="ts" setup>
+const props = defineProps({
+  // 上传接口
+  api: {
+    type: Function,
+    required: true
+  },
+  // 上传额外参数
+  extraData: {
+    type: Object,
+    default: () => ({})
+  },
+  // 按钮文字
+  buttonText: {
+    type: String,
+    default: '导入'
+  }
+})
+
+const emits = defineEmits(['success'])
+
+const upload = async (params) => {
+  console.log(params)
+  const { file } = params
+  const formData = new FormData()
+  formData.append('file', file)
+  for (const i in props.extraData) {
+    formData.append(i, props.extraData[i])
+  }
+  const res = await props.api(formData)
+  console.log(res)
+  if (res?.success || res.data.status === 'success') {
+    ElMessage({
+      type: 'success',
+      message: '导入成功,如数据未刷新,请稍后查看'
+    })
+    setTimeout(() => {
+      emits('success')
+    }, 1000)
+  } else {
+    ElMessage({
+      type: 'error',
+      message: '导入失败'
+    })
+  }
+}
+</script>
+
+<style lang="less">
+.upload-button {
+  display: inline-block;
+  margin: 0 12px;
+}
+</style>

+ 202 - 0
common/src/business-components/Notice.vue

@@ -0,0 +1,202 @@
+<script setup lang="ts">
+import { ref, watch } from 'vue'
+import { useWebSocket } from '@vueuse/core'
+import { ElNotification } from 'element-plus'
+import { useUserStore } from '../store/modules/user'
+import { useDebounceFn } from '@vueuse/core'
+import { useRouter } from 'vue-router'
+const push = useRouter().push
+
+const showDrawer = ref(false)
+
+const handleClick = () => {
+  showDrawer.value = true
+}
+
+const noticeList = ref<Recordable[]>([])
+const isNotice = ref(false)
+
+const wsUrl = import.meta.env.VITE_WS_API
+// 关闭ws连接
+const closeMethod = ref()
+const sendMethod = ref()
+const userStore = useUserStore()
+watch(
+  () => userStore.getToken,
+  (val) => {
+    if (val && wsUrl) {
+      //全局消息提醒
+      const { send, close } = useWebSocket(`${wsUrl}/websocket?accessToken=${val}`, {
+        onConnected: function (ws) {
+          console.log('websocket 连接成功!', ws)
+        },
+        onDisconnected: function (_ws, _event) {
+          console.log('onDisconnected')
+        },
+        onError: function (_ws, _event) {
+          console.log('onError')
+          close()
+        },
+        onMessage: function (_ws, event) {
+          // 记录关闭方法  用于关闭ws连接
+          closeMethod.value = close
+          sendMethod.value = send
+          if (event.data) {
+            const data = JSON.parse(event.data)
+
+            // 根据appCode 来过滤ws的提示内容
+            const flag = sessionStorage.getItem('flag')
+            // if (flag && flag !== data.extra.appCode) return
+            if (flag && !flag.includes(data.extra.appCode)) return
+
+            // 推送来的消息存入 已推送过的不再存
+            if (
+              !noticeList.value.length ||
+              !noticeList.value.find((item) => item.extra.msgId === data.extra.msgId)
+            ) {
+              noticeList.value.push(data)
+            }
+            // 多次推送的消息 只提醒一次
+            if (!isNotice.value) {
+              const elNotice = ElNotification({
+                title: '消息提醒',
+                type: 'warning',
+                duration: 20000,
+                customClass: 'ws-msg',
+                dangerouslyUseHTMLString: true,
+                onClick: () => {
+                  elNotice.close()
+                  showDrawer.value = true
+                },
+                // 门户页 不跳转   没path 不跳转
+                message: `<div class="msg-box">
+                              您有新的消息提醒
+                              <div class="msg-box-read">点击查看</div>
+                            </div>`
+              })
+              isNotice.value = true
+            }
+            noticeDebounce()
+          }
+        },
+        heartbeat: {
+          message: 'ping',
+          interval: 600000,
+          pongTimeout: 600000
+        },
+        autoReconnect: true,
+        autoClose: true
+      })
+    } else {
+      // 取消链接
+      closeMethod.value && closeMethod.value()
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+const noticeDebounce = useDebounceFn(() => {
+  isNotice.value = false
+}, 10000)
+
+// 点击标为已读
+const handleCLickRead = (item, index: number, router: boolean = false) => {
+  // 为true 走跳转
+  if (router) {
+    push(item.extra.path)
+    showDrawer.value = false
+  }
+
+  const sendData = {
+    bizCode: 'internalMessageRead',
+    content: item?.extra?.msgId
+  }
+  sendMethod.value && sendMethod.value(JSON.stringify(sendData))
+  noticeList.value.splice(index, 1)
+}
+const handleClickReadAll = () => {
+  ElMessageBox.confirm('是否确认将所有标记为已读?', '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(() => {
+      for (const item of noticeList.value) {
+        const sendData = {
+          bizCode: 'internalMessageRead',
+          content: item?.extra?.msgId
+        }
+        sendMethod.value && sendMethod.value(JSON.stringify(sendData))
+      }
+      noticeList.value = []
+    })
+    .catch(() => {})
+}
+</script>
+
+<template>
+  <el-badge :is-dot="!!noticeList.length" class="mr-16px cursor-pointer" @click="handleClick">
+    <el-icon size="20px" class="align-middle"><Bell /></el-icon>
+    <el-drawer v-model="showDrawer" title="消息提醒" custom-class="notice-drawer">
+      <el-empty description="暂无提醒" v-if="!noticeList.length" />
+      <div class="ws-msg-list" v-else>
+        <div class="overflow-hidden">
+          <BaseButton size="small" class="mb-10px float-end" @click="handleClickReadAll"
+            >所有标为已读</BaseButton
+          >
+        </div>
+        <div
+          v-for="(item, index) in noticeList"
+          :key="index"
+          class="ws-msg-item flex justify-between align-center gap-10px"
+        >
+          <div class="ws-msg-item-title">
+            <el-icon color="#999" size="16px" class="align-middle mr-6px"><Bell /></el-icon>
+            {{ item.content }}
+            <div
+              ><BaseButton
+                link
+                type="primary"
+                @click="handleCLickRead(item, index, true)"
+                v-if="item.extra.path"
+                >点击跳转</BaseButton
+              ></div
+            >
+          </div>
+          <BaseButton link type="primary" @click="handleCLickRead(item, index)"
+            >标为已读</BaseButton
+          >
+        </div>
+      </div>
+    </el-drawer>
+  </el-badge>
+</template>
+
+<style lang="less" scoped>
+:deep(.el-drawer) {
+  .el-drawer__header {
+    margin-bottom: 10px;
+  }
+}
+.ws-msg-list {
+  .ws-msg-item {
+    font-size: 14px;
+    & + .ws-msg-item {
+      margin-top: 10px;
+      border-top: 1px solid var(--el-border-color-light);
+      padding-top: 10px;
+    }
+  }
+}
+</style>
+<style lang="less">
+.ws-msg {
+  .msg-box-read {
+    margin-top: 2px;
+    color: var(--el-color-primary);
+    cursor: pointer;
+  }
+}
+</style>

+ 93 - 0
common/src/business-components/SelectDept.vue

@@ -0,0 +1,93 @@
+<script lang="tsx">
+/*
+ * 业务组件 勾选部门
+ * */
+import { defineComponent, ref, watch, unref, watchEffect, PropType } from 'vue'
+import { getDeptTree } from '../api'
+import type { DepartmentItem } from '../api/types/custom'
+
+export default defineComponent({
+  name: 'SelectDept',
+  props: {
+    // 树形数据配置
+    props: {
+      type: Object,
+      default: () => {}
+    },
+    // 默认选中的部门id
+    selected: {
+      type: Array<string | number>,
+      default: () => []
+    },
+    useExternalTree: {
+      type: Boolean,
+      default: false
+    },
+    // 树形数据
+    // 如果传了,则直接使用,否则调用接口获取
+    externalTree: {
+      type: Array as PropType<DepartmentItem[]>,
+      default: () => []
+    }
+  },
+  setup(props, { expose }) {
+    const depts = ref<any[]>([])
+    const getDeptTreeFunc = async () => {
+      const { data, success } = await getDeptTree({})
+      if (success) depts.value = data
+    }
+    // 如果useExternalTree为真,则直接使用,否则调用接口获取
+    watchEffect(() => {
+      if (props.useExternalTree) {
+        depts.value = props.externalTree || []
+      } else getDeptTreeFunc()
+    })
+
+    const ComponentRef = ref()
+    const defaultProps = {
+      label: 'name'
+    }
+    const filterText = ref('')
+    const filterNode = (value: string, data) => {
+      if (!value) return true
+      return data[defaultProps.label].includes(value)
+    }
+    watch(filterText, (val) => {
+      ComponentRef.value!.filter(val)
+    })
+
+    const getData = () => {
+      return unref(ComponentRef).getCheckedKeys()
+    }
+    expose({
+      getData
+    })
+    return () => {
+      return (
+        <div>
+          <el-input
+            vModel={filterText.value}
+            clearable
+            style="width: 240px"
+            class="mb-16px"
+            placeholder="筛选部门名称"
+          />
+          <el-tree
+            ref={(el) => (ComponentRef.value = el)}
+            data={depts.value}
+            props={defaultProps}
+            show-checkbox
+            check-strictly
+            node-key={'id'}
+            default-checked-keys={props.selected}
+            filter-node-method={(value, data) => filterNode(value, data)}
+            {...props.props}
+          />
+        </div>
+      )
+    }
+  }
+})
+</script>
+
+<style scoped></style>

+ 373 - 0
common/src/business-components/SelectUser/SelectUser.vue

@@ -0,0 +1,373 @@
+<!-- 配置人员 组件 -->
+
+<script setup lang="tsx">
+import { ContentWrap } from '../../components/ContentWrap'
+import { Table } from '../../components/Table'
+import { ref, unref, nextTick, watch, reactive, toRaw, PropType } from 'vue'
+import { ElTree, ElInput, ElDivider, ElMessage } from 'element-plus'
+import type { DepartmentItem } from '../../api/types/custom'
+import { useTable } from '../../hooks/web/useTable'
+import { Search } from '../../components/Search'
+import { CrudSchema, useCrudSchemas } from '../../hooks/web/useCrudSchemas'
+import { getUserList, getDeptTree } from '../../api'
+import { ISelectUser } from './types'
+import { Recordable } from 'vite-plugin-mock'
+
+const props = defineProps({
+  // 原先已选择的人员
+  selectUsers: {
+    type: Array as PropType<ISelectUser[]>,
+    default: () => []
+  },
+  // 引用组件往外暴露的所使用的key值
+  userKey: {
+    type: String,
+    default: 'userId'
+  },
+  // 额外的人员请求接口
+  extraTableAxios: {
+    type: [Function, null],
+    default: null
+  },
+  // 额外的人员请求接口参数
+  extraTableAxiosParams: {
+    type: Object as PropType<Recordable>,
+    default: () => ({})
+  },
+  // 额外的tree
+  extraTree: {
+    type: Array as PropType<DepartmentItem[]>,
+    default: () => null
+  },
+  // 树的属性
+  treeProps: {
+    type: Object as PropType<Recordable>,
+    default: () => ({})
+  },
+  //   树是否只支持勾选,不支持点选
+  isCheckSelect: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { pageSize, currentPage } = tableState
+    const promise = props.extraTableAxios || getUserList
+
+    const res = await promise({
+      userName: unref(searchParams)?.userName,
+      deptId: unref(currentNodeKey),
+      deptIdList: props.isCheckSelect ? unref(selectDeptIds) : undefined,
+      current: unref(currentPage),
+      size: unref(pageSize),
+      ...unref(searchParams),
+      ...props.extraTableAxiosParams
+    })
+    return {
+      list: res.data.list || [],
+      total: Number(res.data.totalCount) || 0
+    }
+  }
+})
+const { total, loading, dataList, pageSize, currentPage } = tableState
+const { getList } = tableMethods
+
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'selection',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'selection'
+    }
+  },
+  {
+    field: 'index',
+    label: '序号',
+    form: {
+      hidden: true
+    },
+    search: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'index'
+    }
+  },
+  {
+    field: 'userName',
+    label: '姓名'
+  },
+  {
+    field: 'userNo',
+    label: '账号',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'phone',
+    label: '手机号',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'email',
+    label: '邮箱',
+    search: {
+      hidden: true
+    }
+  }
+])
+
+const { allSchemas } = useCrudSchemas(crudSchemas)
+
+const selectedUsers = ref<ISelectUser[]>([])
+
+const tableRef = ref()
+
+const searchParams = ref<any>({})
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
+
+const treeEl = ref<typeof ElTree>()
+
+const currentNodeKey = ref('')
+const departmentList = ref<DepartmentItem[]>([])
+const fetchDepartment = async () => {
+  const res = await getDeptTree({})
+  departmentList.value = res.data
+}
+
+if (props.extraTree) {
+  departmentList.value = props.extraTree
+} else fetchDepartment()
+
+const currentDepartment = ref('')
+watch(
+  () => currentDepartment.value,
+  (val) => {
+    unref(treeEl)!.filter(val)
+  }
+)
+const filterNode = (value: string, data: any) => {
+  if (!value) return true
+  return data?.name.includes(value)
+}
+
+// 部门点击
+const currentChange = (data: DepartmentItem) => {
+  if (props.isCheckSelect) return
+
+  currentNodeKey.value = toRaw(data).id
+  currentPage.value = 1
+  getList()
+}
+
+// 部门勾选 初始值为-1 为了不加载全部人员
+const selectDeptIds = ref<(string | number)[]>([-1])
+const checkChange = (_data, nodes) => {
+  const { checkedNodes } = nodes
+  if (checkedNodes.length) {
+    selectDeptIds.value = checkedNodes.filter((i) => !i.disabled).map((j) => j.id)
+  } else {
+    selectDeptIds.value = [-1]
+  }
+  currentPage.value = 1
+  getList()
+}
+
+// 已选择的从外面传进来
+watch(
+  () => props.selectUsers,
+  (val) => {
+    selectedUsers.value = val
+  },
+  {
+    immediate: true
+  }
+)
+
+const handleSelect = (selection, row) => {
+  const dataIndex = selection.findIndex((item) => item[props.userKey] === row[props.userKey])
+  // 如果当前操作行不在选中selection里,则对containerList数据进行删除相应行数据
+  if (dataIndex === -1) {
+    const index = selectedUsers.value.findIndex(
+      (item) => item[props.userKey] === row[props.userKey]
+    )
+    selectedUsers.value.splice(index, 1)
+  } else {
+    selectedUsers.value.push(row)
+  }
+}
+
+const submit = async () => {
+  const arr: any[] = []
+  toRaw(selectedUsers.value).forEach((item) => {
+    arr.push(item?.[props.userKey])
+  })
+  if (arr?.length === 0) {
+    ElMessage.warning('请选择人员')
+    return null
+  }
+  return {
+    userIds: arr,
+    selectItem: selectedUsers.value
+  }
+}
+
+// 选择表格行
+const editTableSelect = async () => {
+  await nextTick()
+  toRaw(selectedUsers?.value)?.forEach((item) => {
+    // 如果表格中数据和containerList中数据userId有相同,则勾选对应行的checkbox
+    const index = toRaw(dataList.value)?.findIndex((dataItem) => {
+      return dataItem?.[props.userKey] === item?.[props.userKey]
+    })
+    index !== -1 &&
+      toRaw(tableRef.value?.elTableRef).toggleRowSelection(toRaw(dataList.value[index]), true)
+  })
+}
+
+const del = (item) => {
+  const index = selectedUsers?.value.findIndex((it) => it[props.userKey] === item[props.userKey])
+  selectedUsers.value.splice(index, 1)
+  const editIndex = toRaw(dataList.value)?.findIndex((dataItem) => {
+    return dataItem?.[props.userKey] === item?.[props.userKey]
+  })
+  editIndex !== -1 &&
+    toRaw(tableRef.value?.elTableRef).toggleRowSelection(toRaw(dataList.value[editIndex]), false)
+}
+
+defineExpose({
+  submit
+})
+
+watch(
+  () => dataList.value,
+  async () => {
+    await editTableSelect()
+  },
+  {
+    deep: true
+  }
+)
+</script>
+
+<template>
+  <div class="flex w-100% h-100%">
+    <ContentWrap class="w-250px !overflow-y-auto">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">部门列表</div>
+        <ElInput v-model="currentDepartment" class="flex-[2]" placeholder="搜索部门" clearable />
+      </div>
+      <ElDivider />
+      <div class="treeContainer">
+        <ElTree
+          ref="treeEl"
+          :data="departmentList"
+          default-expand-all
+          :expand-on-click-node="false"
+          node-key="id"
+          :current-node-key="currentNodeKey"
+          :props="{
+            label: 'name'
+          }"
+          v-bind="props.treeProps"
+          :filter-node-method="filterNode"
+          @current-change="currentChange"
+          @check="checkChange"
+        >
+          <template #default="{ data }">
+            <div :title="data.name" class="whitespace-nowrap overflow-ellipsis overflow-hidden">
+              {{ data.name }}
+            </div>
+          </template>
+        </ElTree>
+      </div>
+    </ContentWrap>
+    <ContentWrap class="flex-[3] ml-20px !overflow-y-auto">
+      <Search
+        :schema="allSchemas.searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+      />
+      <Table
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :columns="allSchemas.tableColumns"
+        :data="dataList"
+        :loading="loading"
+        @register="tableRegister"
+        @select="handleSelect"
+        ref="tableRef"
+        :pagination="{
+          total
+        }"
+        row-key="userNo"
+        height="440px"
+      />
+    </ContentWrap>
+    <ContentWrap class="w-250px ml-20px !overflow-y-auto">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">{{ '已选人员' }}</div>
+      </div>
+      <ElDivider />
+      <div class="selected">
+        <div v-for="(item, i) in selectedUsers" :key="i" class="selectedList">
+          <div class="name">{{ item?.userName || `未知用户名${i + 1}` }}</div>
+          <img @click="del(item)" class="del" src="../../assets/imgs/del.png" alt="" />
+        </div>
+      </div>
+    </ContentWrap>
+  </div>
+</template>
+
+<style scoped lang="less">
+.selectedList {
+  display: flex;
+  padding: 2px 8px;
+  margin-bottom: 10px;
+  cursor: pointer;
+  justify-content: space-between;
+  align-items: center;
+
+  &:hover {
+    background: rgb(102 177 255 / 25%);
+
+    .del {
+      display: block;
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+.del {
+  display: none;
+  width: 16px;
+  height: 16px;
+}
+
+:deep(.el-table) {
+  thead .el-table-column--selection .el-checkbox {
+    display: none;
+  }
+}
+</style>

+ 5 - 0
common/src/business-components/SelectUser/index.ts

@@ -0,0 +1,5 @@
+import SelectUser from './SelectUser.vue'
+
+export type { ISelectUser } from './types'
+
+export { SelectUser }

+ 5 - 0
common/src/business-components/SelectUser/types.ts

@@ -0,0 +1,5 @@
+export interface ISelectUser {
+  userNo: string
+  userId?: number
+  userName?: string
+}

+ 320 - 0
common/src/business-components/Users.vue

@@ -0,0 +1,320 @@
+<!-- 配置人员 组件 -->
+
+<script setup lang="tsx">
+import { ContentWrap } from '../components/ContentWrap'
+import { useI18n } from '../hooks/web/useI18n'
+import { Table } from '../components/Table'
+import { ref, unref, nextTick, watch, reactive, toRaw, PropType } from 'vue'
+import { ElTree, ElInput, ElDivider, ElMessage } from 'element-plus'
+import type { DepartmentItem } from '../api/types/custom'
+import { useTable } from '../hooks/web/useTable'
+import { Search } from '../components/Search'
+import { CrudSchema, useCrudSchemas } from '../hooks/web/useCrudSchemas'
+import { getUserList, getDeptTree } from '../api'
+
+const { t } = useI18n()
+
+const props = defineProps({
+  // 原先已选择的人员
+  selectUsers: {
+    type: Array as PropType<any>,
+    default: () => []
+  }
+})
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { pageSize, currentPage } = tableState
+    const res = await getUserList({
+      userName: unref(searchParams)?.userName,
+      deptId: unref(currentNodeKey),
+      current: unref(currentPage),
+      size: unref(pageSize),
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list || [],
+      total: Number(res.data.totalCount) || 0
+    }
+  }
+})
+const { total, loading, dataList, pageSize, currentPage } = tableState
+const { getList } = tableMethods
+
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'selection',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'selection'
+    }
+  },
+  {
+    field: 'index',
+    label: t('userDemo.index'),
+    form: {
+      hidden: true
+    },
+    search: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      type: 'index'
+    }
+  },
+  {
+    field: 'userName',
+    label: t('userDemo.username')
+  },
+  {
+    field: 'userNo',
+    label: t('userDemo.account'),
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'phone',
+    label: '手机号',
+    search: {
+      hidden: true
+    }
+  },
+  {
+    field: 'email',
+    label: '邮箱',
+    search: {
+      hidden: true
+    }
+  }
+])
+
+const { allSchemas } = useCrudSchemas(crudSchemas)
+
+const selectedUsers = ref<any>([])
+
+const tableRef = ref()
+
+const searchParams = ref<any>({})
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
+
+const treeEl = ref<typeof ElTree>()
+
+const currentNodeKey = ref('')
+const departmentList = ref<DepartmentItem[]>([])
+const fetchDepartment = async () => {
+  const res = await getDeptTree({})
+  departmentList.value = res.data
+}
+fetchDepartment()
+
+const currentDepartment = ref('')
+watch(
+  () => currentDepartment.value,
+  (val) => {
+    unref(treeEl)!.filter(val)
+  }
+)
+
+const currentChange = (data: DepartmentItem) => {
+  currentNodeKey.value = toRaw(data).id
+  currentPage.value = 1
+  getList()
+}
+
+const filterNode = (value: string, data: any) => {
+  if (!value) return true
+  return data?.name.includes(value)
+}
+
+// 已选择的从外面传进来
+watch(
+  () => props.selectUsers,
+  (val) => {
+    selectedUsers.value = val
+  },
+  {
+    immediate: true
+  }
+)
+
+const handleSelect = (selection, row) => {
+  const dataIndex = selection.findIndex((item) => item.userId === row.userId)
+  // 如果当前操作行不在选中selection里,则对containerList数据进行删除相应行数据
+  if (dataIndex === -1) {
+    const index = selectedUsers.value.findIndex((item) => item.userId === row.userId)
+    selectedUsers.value.splice(index, 1)
+  } else {
+    selectedUsers.value.push(row)
+  }
+}
+
+const submit = async () => {
+  const arr: any[] = []
+  toRaw(selectedUsers.value).forEach((item) => {
+    arr.push(item?.userId)
+  })
+  if (arr?.length === 0) {
+    ElMessage.warning('请选择人员')
+    return null
+  }
+  return {
+    userIds: arr
+  }
+}
+
+const editTableSelect = async () => {
+  await nextTick()
+  toRaw(selectedUsers?.value)?.forEach((item) => {
+    // 如果表格中数据和containerList中数据userId有相同,则勾选对应行的checkbox
+    const index = toRaw(dataList.value)?.findIndex((dataItem) => {
+      return dataItem?.userId === item?.userId
+    })
+    index !== -1 &&
+      toRaw(tableRef.value?.elTableRef).toggleRowSelection(toRaw(dataList.value[index]), true)
+  })
+}
+
+const del = (item) => {
+  const index = selectedUsers?.value.findIndex((it) => it.userId === item.userId)
+  selectedUsers.value.splice(index, 1)
+  const editIndex = toRaw(dataList.value)?.findIndex((dataItem) => {
+    return dataItem?.userId === item?.userId
+  })
+  editIndex !== -1 &&
+    toRaw(tableRef.value?.elTableRef).toggleRowSelection(toRaw(dataList.value[editIndex]), false)
+}
+
+defineExpose({
+  submit
+})
+
+watch(
+  () => dataList.value,
+  async () => {
+    editTableSelect()
+  },
+  {
+    deep: true
+    // immediate: true
+  }
+)
+</script>
+
+<template>
+  <div class="flex w-100% h-100%">
+    <ContentWrap class="w-250px !overflow-y-auto">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
+        <ElInput
+          v-model="currentDepartment"
+          class="flex-[2]"
+          :placeholder="t('userDemo.searchDepartment')"
+          clearable
+        />
+      </div>
+      <ElDivider />
+      <div class="treeContainer">
+        <ElTree
+          ref="treeEl"
+          :data="departmentList"
+          default-expand-all
+          :expand-on-click-node="false"
+          node-key="id"
+          :current-node-key="currentNodeKey"
+          :props="{
+            label: 'name'
+          }"
+          :filter-node-method="filterNode"
+          @current-change="currentChange"
+        >
+          <template #default="{ data }">
+            <div :title="data.name" class="whitespace-nowrap overflow-ellipsis overflow-hidden">
+              {{ data.name }}
+            </div>
+          </template>
+        </ElTree>
+      </div>
+    </ContentWrap>
+    <ContentWrap class="flex-[3] ml-20px !overflow-y-auto">
+      <Search
+        :schema="allSchemas.searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+      />
+      <Table
+        v-model:current-page="currentPage"
+        v-model:page-size="pageSize"
+        :columns="allSchemas.tableColumns"
+        :data="dataList"
+        :loading="loading"
+        @register="tableRegister"
+        @select="handleSelect"
+        ref="tableRef"
+        :pagination="{
+          total
+        }"
+      />
+    </ContentWrap>
+    <ContentWrap class="w-250px ml-20px !overflow-y-auto">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">{{ '已选人员' }}</div>
+      </div>
+      <ElDivider />
+      <div class="selected">
+        <div v-for="(item, i) in selectedUsers" :key="i" class="selectedList">
+          <div class="name">{{ item.userName }}</div>
+          <img @click="del(item)" class="del" src="../assets/imgs/del.png" alt="" />
+        </div>
+      </div>
+    </ContentWrap>
+  </div>
+</template>
+
+<style scoped lang="less">
+.selectedList {
+  display: flex;
+  padding: 2px 8px;
+  margin-bottom: 10px;
+  cursor: pointer;
+  justify-content: space-between;
+  align-items: center;
+
+  &:hover {
+    background: rgb(102 177 255 / 25%);
+
+    .del {
+      display: block;
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+.del {
+  display: none;
+  width: 16px;
+  height: 16px;
+}
+
+:deep(.el-table) {
+  thead .el-table-column--selection .el-checkbox {
+    display: none;
+  }
+}
+</style>

+ 3 - 0
common/src/components/Backtop/index.ts

@@ -0,0 +1,3 @@
+import Backtop from './src/Backtop.vue'
+
+export {Backtop}

+ 22 - 0
common/src/components/Backtop/src/Backtop.vue

@@ -0,0 +1,22 @@
+<script setup lang="ts">
+import {ElBacktop} from 'element-plus'
+import {useDesign} from '../../../hooks/web/useDesign'
+
+const {getPrefixCls, variables} = useDesign()
+
+const prefixCls = getPrefixCls('backtop')
+</script>
+
+<template>
+  <ElBacktop
+      :class="prefixCls"
+      :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
+  />
+</template>
+
+<style lang="less">
+.el-backtop {
+  z-index: 99;
+  bottom: 80px !important;
+}
+</style>

+ 3 - 0
common/src/components/Breadcrumb/index.ts

@@ -0,0 +1,3 @@
+import Breadcrumb from './src/Breadcrumb.vue'
+
+export { Breadcrumb }

+ 127 - 0
common/src/components/Breadcrumb/src/Breadcrumb.vue

@@ -0,0 +1,127 @@
+<script lang="tsx">
+import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
+import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
+import { useRouter } from 'vue-router'
+import { usePermissionStore } from '../../../store/modules/permission'
+import { filterBreadcrumb } from './helper'
+import { filter, treeToList } from '../../../utils/tree'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import { useI18n } from '../../../hooks/web/useI18n'
+import { Icon } from '../../Icon'
+import { useAppStore } from '../../../store/modules/app'
+import { useDesign } from '../../../hooks/web/useDesign'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('breadcrumb')
+
+const appStore = useAppStore()
+
+// 面包屑图标
+const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
+
+export default defineComponent({
+  name: 'Breadcrumb',
+  setup() {
+    const { currentRoute } = useRouter()
+
+    const { t } = useI18n()
+
+    const levelList = ref<AppRouteRecordRaw[]>([])
+
+    const permissionStore = usePermissionStore()
+
+    const menuRouters = computed(() => {
+      const routers = permissionStore.getRouters
+      return filterBreadcrumb(routers)
+    })
+
+    const getBreadcrumb = () => {
+      const currentPath = currentRoute.value.matched.slice(-1)[0].path
+      levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
+        return node.path === currentPath
+      })
+    }
+
+    const renderBreadcrumb = () => {
+      const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
+      return breadcrumbList.map((v, index) => {
+        const disabled = !v.redirect || v.redirect === 'noredirect'
+        const meta = v.meta
+        return (
+          <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name || index}>
+            {meta?.icon && breadcrumbIcon.value ? (
+              <>
+                <Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
+              </>
+            ) : (
+              t(v?.meta?.title || '')
+            )}
+          </ElBreadcrumbItem>
+        )
+      })
+    }
+
+    watch(
+      () => currentRoute.value,
+      (route: RouteLocationNormalizedLoaded) => {
+        if (route.path.startsWith('/redirect/')) {
+          return
+        }
+        getBreadcrumb()
+      },
+      {
+        immediate: true
+      }
+    )
+
+    return () => (
+      <ElBreadcrumb separator="/" class={`${prefixCls} flex items-center h-full ml-[10px]`}>
+        <TransitionGroup appear enter-active-class="animate__animated animate__fadeInRight">
+          {renderBreadcrumb()}
+        </TransitionGroup>
+      </ElBreadcrumb>
+    )
+  }
+})
+</script>
+
+<style lang="less" scoped>
+@prefix-cls: ~'@{elNamespace}-breadcrumb';
+
+.@{prefix-cls} {
+  :deep(&__item) {
+    display: flex;
+
+    .@{prefix-cls}__inner {
+      display: flex;
+      align-items: center;
+      color: var(--top-header-text-color);
+
+      &:hover {
+        color: var(--el-color-primary);
+      }
+    }
+  }
+
+  :deep(&__item):not(:last-child) {
+    .@{prefix-cls}__inner {
+      color: var(--top-header-text-color);
+
+      &:hover {
+        color: var(--el-color-primary);
+      }
+    }
+  }
+
+  :deep(&__item):last-child {
+    .@{prefix-cls}__inner {
+      color: var(--el-text-color-placeholder);
+
+      &:hover {
+        color: var(--el-text-color-placeholder);
+      }
+    }
+  }
+}
+</style>

+ 30 - 0
common/src/components/Breadcrumb/src/helper.ts

@@ -0,0 +1,30 @@
+import { pathResolve } from '@/utils/routerHelper'
+
+export const filterBreadcrumb = (
+  routes: AppRouteRecordRaw[],
+  parentPath = ''
+): AppRouteRecordRaw[] => {
+  const res: AppRouteRecordRaw[] = []
+
+  for (const route of routes) {
+    const meta = route?.meta
+    if (meta.hidden && !meta.canTo) {
+      continue
+    }
+
+    const data: AppRouteRecordRaw =
+      !meta.alwaysShow && route.children?.length === 1
+        ? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }
+        : { ...route }
+
+    data.path = pathResolve(parentPath, data.path)
+
+    if (data.children) {
+      data.children = filterBreadcrumb(data.children, data.path)
+    }
+    if (data) {
+      res.push(data)
+    }
+  }
+  return res
+}

+ 3 - 0
common/src/components/Button/index.ts

@@ -0,0 +1,3 @@
+import BaseButton from './src/Button.vue'
+
+export {BaseButton}

+ 121 - 0
common/src/components/Button/src/Button.vue

@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import {useDesign} from '../../../hooks/web/useDesign'
+import {ElButton, ComponentSize, ButtonType} from 'element-plus'
+import {PropType, Component, computed, unref} from 'vue'
+import {useAppStore} from '../../../store/modules/app'
+
+const appStore = useAppStore()
+
+const getTheme = computed(() => appStore.getTheme)
+
+const {getPrefixCls} = useDesign()
+
+const prefixCls = getPrefixCls('button')
+
+const props = defineProps({
+  size: {
+    type: String as PropType<ComponentSize>,
+    default: undefined
+  },
+  type: {
+    type: String as PropType<ButtonType>,
+    default: 'default'
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },
+  plain: {
+    type: Boolean,
+    default: false
+  },
+  text: {
+    type: Boolean,
+    default: false
+  },
+  bg: {
+    type: Boolean,
+    default: false
+  },
+  link: {
+    type: Boolean,
+    default: false
+  },
+  round: {
+    type: Boolean,
+    default: false
+  },
+  circle: {
+    type: Boolean,
+    default: false
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  },
+  loadingIcon: {
+    type: [String, Object] as PropType<String | Component>,
+    default: undefined
+  },
+  icon: {
+    type: [String, Object] as PropType<String | Component>,
+    default: undefined
+  },
+  autofocus: {
+    type: Boolean,
+    default: false
+  },
+  nativeType: {
+    type: String as PropType<'button' | 'submit' | 'reset'>,
+    default: 'button'
+  },
+  autoInsertSpace: {
+    type: Boolean,
+    default: false
+  },
+  color: {
+    type: String,
+    default: ''
+  },
+  darker: {
+    type: Boolean,
+    default: false
+  },
+  tag: {
+    type: [String, Object] as PropType<String | Component>,
+    default: 'button'
+  }
+})
+
+const emits = defineEmits(['click'])
+
+const color = computed(() => {
+  const {type, link} = props
+  if (type === 'primary' && !link) {
+    return unref(getTheme).elColorPrimary
+  }
+  return ''
+})
+
+const style = computed(() => {
+  const {type, link, plain} = props
+  if (type === 'primary' && !link && !plain) {
+    return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
+  }
+  return ''
+})
+</script>
+
+<template>
+  <ElButton
+      :class="`${prefixCls} color-#fff`"
+      v-bind="{ ...props }"
+      :color="color"
+      :style="style"
+      @click="() => emits('click')"
+  >
+    <slot></slot>
+    <slot name="icon"></slot>
+    <slot name="loading"></slot>
+  </ElButton>
+</template>

+ 3 - 0
common/src/components/Collapse/index.ts

@@ -0,0 +1,3 @@
+import Collapse from './src/Collapse.vue'
+
+export { Collapse }

+ 34 - 0
common/src/components/Collapse/src/Collapse.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+import { computed, unref } from 'vue'
+import { useAppStore } from '../../../store/modules/app'
+import { propTypes } from '../../../utils/propTypes'
+import { useDesign } from '../../../hooks/web/useDesign'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('collapse')
+
+defineProps({
+  color: propTypes.string.def('')
+})
+
+const appStore = useAppStore()
+
+const collapse = computed(() => appStore.getCollapse)
+
+const toggleCollapse = () => {
+  const collapsed = unref(collapse)
+  appStore.setCollapse(!collapsed)
+}
+</script>
+
+<template>
+  <div :class="prefixCls" @click="toggleCollapse">
+    <Icon
+      :size="18"
+      :icon="collapse ? 'vi-ant-design:menu-unfold-outlined' : 'vi-ant-design:menu-fold-outlined'"
+      :color="color"
+      class="cursor-pointer"
+    />
+  </div>
+</template>

+ 5 - 0
common/src/components/ConfigGlobal/index.ts

@@ -0,0 +1,5 @@
+import ConfigGlobal from './src/ConfigGlobal.vue'
+
+export type { ConfigGlobalTypes } from './src/types'
+
+export { ConfigGlobal }

+ 62 - 0
common/src/components/ConfigGlobal/src/ConfigGlobal.vue

@@ -0,0 +1,62 @@
+<script setup lang="ts">
+import { provide, computed, watch, onMounted } from 'vue'
+import { propTypes } from '../../../utils/propTypes'
+import { ComponentSize, ElConfigProvider } from 'element-plus'
+import { useLocaleStore } from '../../../store/modules/locale'
+import { useWindowSize } from '@vueuse/core'
+import { useAppStore } from '../../../store/modules/app'
+import { setCssVar } from '../../../utils'
+import { useDesign } from '../../../hooks/web/useDesign'
+
+const { variables } = useDesign()
+
+const appStore = useAppStore()
+
+const props = defineProps({
+  size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
+})
+
+provide('configGlobal', props)
+
+// 初始化所有主题色
+onMounted(() => {
+  appStore.setCssVarTheme()
+})
+
+const { width } = useWindowSize()
+
+// 监听窗口变化
+watch(
+  () => width.value,
+  (width: number) => {
+    if (width < 768) {
+      !appStore.getMobile ? appStore.setMobile(true) : undefined
+      setCssVar('--left-menu-min-width', '0')
+      appStore.setCollapse(true)
+      appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined
+    } else {
+      appStore.getMobile ? appStore.setMobile(false) : undefined
+      setCssVar('--left-menu-min-width', '64px')
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+// 多语言相关
+const localeStore = useLocaleStore()
+
+const currentLocale = computed(() => localeStore.currentLocale)
+</script>
+
+<template>
+  <ElConfigProvider
+    :namespace="variables.elNamespace"
+    :locale="currentLocale.elLocale"
+    :message="{ max: 1 }"
+    :size="size"
+  >
+    <slot></slot>
+  </ElConfigProvider>
+</template>

+ 5 - 0
common/src/components/ConfigGlobal/src/types/index.ts

@@ -0,0 +1,5 @@
+import { ComponentSize } from 'element-plus'
+
+export interface ConfigGlobalTypes {
+  size?: ComponentSize
+}

+ 3 - 0
common/src/components/ContentWrap/index.ts

@@ -0,0 +1,3 @@
+import ContentWrap from './src/ContentWrap.vue'
+
+export { ContentWrap }

+ 56 - 0
common/src/components/ContentWrap/src/ContentWrap.vue

@@ -0,0 +1,56 @@
+<script setup lang="ts">
+import { ElCard, ElTooltip } from 'element-plus'
+import { propTypes } from '../../../utils/propTypes'
+import { useDesign } from '../../../hooks/web/useDesign'
+import { useRouter } from 'vue-router'
+
+const { go } = useRouter()
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('content-wrap')
+
+defineProps({
+  title: propTypes.string.def(''),
+  message: propTypes.string.def(''),
+  showBack: propTypes.bool.def(false)
+})
+</script>
+
+<template>
+  <ElCard :class="[prefixCls]" shadow="never">
+    <template v-if="title" #header>
+      <div class="flex items-center">
+        <div class="flex mr-20px back-button" v-if="showBack !== false">
+          <BaseButton icon="ArrowLeft" @click="go(-1)">返回</BaseButton>
+        </div>
+        <span class="text-16px font-700">{{ title }}</span>
+        <ElTooltip v-if="message" effect="dark" placement="right">
+          <template #content>
+            <div class="max-w-200px">{{ message }}</div>
+          </template>
+          <Icon class="ml-5px" icon="vi-bi:question-circle-fill" :size="14" />
+        </ElTooltip>
+        <div class="flex pl-20px flex-grow">
+          <slot name="header"></slot>
+        </div>
+        <!-- 右侧部分-->
+        <div class="ml-20px">
+          <slot name="right"></slot>
+        </div>
+      </div>
+    </template>
+    <div>
+      <slot></slot>
+    </div>
+  </ElCard>
+</template>
+
+<style scoped lang="less">
+// 弹窗里 不显示返回按钮
+.el-dialog {
+  .back-button {
+    display: none;
+  }
+}
+</style>

+ 12 - 0
common/src/components/ContextMenu/index.ts

@@ -0,0 +1,12 @@
+import ContextMenu from './src/ContextMenu.vue'
+import { ElDropdown } from 'element-plus'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+
+export type { ContextMenuSchema } from './src/types'
+
+export interface ContextMenuExpose {
+  elDropdownMenuRef: ComponentRef<typeof ElDropdown>
+  tagItem: RouteLocationNormalizedLoaded
+}
+
+export { ContextMenu }

+ 74 - 0
common/src/components/ContextMenu/src/ContextMenu.vue

@@ -0,0 +1,74 @@
+<script setup lang="ts">
+import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
+import { PropType, ref } from 'vue'
+import { useI18n } from '../../../hooks/web/useI18n'
+import { useDesign } from '../../../hooks/web/useDesign'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import { ContextMenuSchema } from './types'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('context-menu')
+
+const { t } = useI18n()
+
+const emit = defineEmits(['visibleChange'])
+
+const props = defineProps({
+  schema: {
+    type: Array as PropType<ContextMenuSchema[]>,
+    default: () => []
+  },
+  trigger: {
+    type: String as PropType<'click' | 'hover' | 'focus' | 'contextmenu'>,
+    default: 'contextmenu'
+  },
+  tagItem: {
+    type: Object as PropType<RouteLocationNormalizedLoaded>,
+    default: () => ({})
+  }
+})
+
+const command = (item: ContextMenuSchema) => {
+  item.command && item.command(item)
+}
+
+const visibleChange = (visible: boolean) => {
+  emit('visibleChange', visible, props.tagItem)
+}
+
+const elDropdownMenuRef = ref<ComponentRef<typeof ElDropdown>>()
+
+defineExpose({
+  elDropdownMenuRef,
+  tagItem: props.tagItem
+})
+</script>
+
+<template>
+  <ElDropdown
+    ref="elDropdownMenuRef"
+    :class="prefixCls"
+    :trigger="trigger"
+    placement="bottom-start"
+    @command="command"
+    @visible-change="visibleChange"
+    popper-class="v-context-menu-popper"
+  >
+    <slot></slot>
+    <template #dropdown>
+      <ElDropdownMenu>
+        <ElDropdownItem
+          v-for="(item, index) in schema"
+          :key="`dropdown${index}`"
+          :divided="item.divided"
+          :disabled="item.disabled"
+          :command="item"
+        >
+          <Icon :icon="item.icon" />
+          {{ t(item.label) }}
+        </ElDropdownItem>
+      </ElDropdownMenu>
+    </template>
+  </ElDropdown>
+</template>

+ 7 - 0
common/src/components/ContextMenu/src/types/index.ts

@@ -0,0 +1,7 @@
+export interface ContextMenuSchema {
+  disabled?: boolean
+  divided?: boolean
+  icon?: string
+  label: string
+  command?: (item: ContextMenuSchema) => void
+}

+ 5 - 0
common/src/components/Descriptions/index.ts

@@ -0,0 +1,5 @@
+import Descriptions from './src/Descriptions.vue'
+
+export type { DescriptionsSchema } from './src/types'
+
+export { Descriptions }

Some files were not shown because too many files changed in this diff