Browse Source

GIS一张图

CzRger 1 year ago
parent
commit
9005f44880
100 changed files with 3246 additions and 0 deletions
  1. 1 0
      .env
  2. 25 0
      .gitignore
  3. 20 0
      alias.config.js
  4. 71 0
      apiProxy.js
  5. 28 0
      index.html
  6. 40 0
      package.json
  7. 29 0
      preBuild.js
  8. 38 0
      src/App.vue
  9. 19 0
      src/api/index.ts
  10. 62 0
      src/api/interceptors.ts
  11. 15 0
      src/api/modules/account.ts
  12. 14 0
      src/api/modules/auth.ts
  13. 16 0
      src/api/modules/common.ts
  14. 22 0
      src/api/modules/config.ts
  15. 22 0
      src/api/modules/daily.ts
  16. 8 0
      src/api/modules/dept.ts
  17. 36 0
      src/api/modules/dict.ts
  18. 17 0
      src/api/modules/login-log.ts
  19. 40 0
      src/api/modules/notice.ts
  20. 17 0
      src/api/modules/oper-log.ts
  21. 8 0
      src/api/modules/sign.ts
  22. 32 0
      src/api/modules/ztpt.ts
  23. 129 0
      src/api/request.ts
  24. BIN
      src/assets/images/cus/cus-tab_type2-active.png
  25. BIN
      src/assets/images/cus/cus-title_type2-icon.png
  26. BIN
      src/assets/images/cus/file-del.png
  27. BIN
      src/assets/images/cus/to-bottom.png
  28. BIN
      src/assets/images/cus/to-right.png
  29. BIN
      src/assets/images/file-type/file-type_default.png
  30. BIN
      src/assets/images/file-type/file-type_excel.png
  31. BIN
      src/assets/images/file-type/file-type_pdf.png
  32. BIN
      src/assets/images/file-type/file-type_ppt.png
  33. BIN
      src/assets/images/file-type/file-type_rar.png
  34. BIN
      src/assets/images/file-type/file-type_txt.png
  35. BIN
      src/assets/images/file-type/file-type_word.png
  36. BIN
      src/assets/images/global/login_bg.jpeg
  37. BIN
      src/assets/images/global/password.png
  38. BIN
      src/assets/images/global/temp.png
  39. BIN
      src/assets/images/global/user.png
  40. BIN
      src/assets/images/layout/background.png
  41. BIN
      src/assets/images/layout/background_home.png
  42. BIN
      src/assets/images/layout/head-bg.png
  43. BIN
      src/assets/images/layout/head-com-avatar.png
  44. BIN
      src/assets/images/layout/head-com-bg.png
  45. BIN
      src/assets/images/layout/head-com-dropdown.png
  46. BIN
      src/assets/images/layout/head-com-info-center.png
  47. BIN
      src/assets/images/layout/head-com-search.png
  48. BIN
      src/assets/images/layout/logo_icon.png
  49. BIN
      src/assets/images/layout/logo_name.png
  50. BIN
      src/assets/images/layout/menu-active-special.png
  51. BIN
      src/assets/images/layout/menu-bg-special.png
  52. BIN
      src/assets/images/layout/menu-item-bg-active.png
  53. BIN
      src/assets/images/layout/menu-item-bg.png
  54. BIN
      src/assets/images/layout/menu-top-title.png
  55. BIN
      src/assets/mock.png
  56. 6 0
      src/assets/svg/business/add.svg
  57. 5 0
      src/assets/svg/business/arrow_1.svg
  58. 8 0
      src/assets/svg/business/arrow_2.svg
  59. 7 0
      src/assets/svg/business/close_1.svg
  60. 8 0
      src/assets/svg/business/close_2.svg
  61. 11 0
      src/assets/svg/business/close_3.svg
  62. 7 0
      src/assets/svg/business/cus-content_field-column-drag.svg
  63. 7 0
      src/assets/svg/business/cus-content_field-column-select.svg
  64. 8 0
      src/assets/svg/business/cus-content_field-column.svg
  65. 11 0
      src/assets/svg/business/cus-content_field-fixed.svg
  66. 11 0
      src/assets/svg/business/cus-content_field-in.svg
  67. 11 0
      src/assets/svg/business/cus-content_tools.svg
  68. 9 0
      src/assets/svg/business/default.svg
  69. 1 0
      src/assets/svg/business/menu-mock.svg
  70. 8 0
      src/assets/svg/business/search.svg
  71. 1 0
      src/assets/svg/business/to_bottom_camera.svg
  72. 1 0
      src/assets/svg/business/to_right_camera.svg
  73. 1 0
      src/assets/svg/business/u212.svg
  74. 17 0
      src/assets/svg/global/关注对象.svg
  75. 11 0
      src/assets/svg/global/字典管理.svg
  76. 16 0
      src/assets/svg/global/安全审计.svg
  77. 13 0
      src/assets/svg/global/权限管理.svg
  78. 11 0
      src/assets/svg/global/标签管理.svg
  79. 1 0
      src/assets/svg/global/水军水手.svg
  80. 23 0
      src/assets/svg/global/港岙口.svg
  81. 13 0
      src/assets/svg/global/用户管理.svg
  82. 1 0
      src/assets/svg/global/网络管理.svg
  83. 13 0
      src/assets/svg/global/船舶管理.svg
  84. 1 0
      src/assets/svg/global/菜单管理.svg
  85. 1 0
      src/assets/svg/global/调度管理.svg
  86. 12 0
      src/assets/svg/global/部门管理.svg
  87. 23 0
      src/assets/svg/global/配置管理.svg
  88. 22 0
      src/assets/svg/global/随船人员.svg
  89. 53 0
      src/components/SvgIcon/index.vue
  90. 580 0
      src/components/cus/CusContent.vue
  91. 115 0
      src/components/cus/CusContentCard.vue
  92. 160 0
      src/components/cus/CusDialog.vue
  93. 67 0
      src/components/cus/CusEllipsis.vue
  94. 107 0
      src/components/cus/CusForm.vue
  95. 455 0
      src/components/cus/CusFormColumn.vue
  96. 92 0
      src/components/cus/CusPopover.vue
  97. 86 0
      src/components/cus/CusSearchButtons.vue
  98. 122 0
      src/components/cus/CusTab.vue
  99. 401 0
      src/components/cus/CusTable.vue
  100. 0 0
      src/components/cus/CusTableCard.vue

+ 1 - 0
.env

@@ -0,0 +1 @@
+VITE_PROJECT_TITLE = 防控席位

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+seat*
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+stats.html
+yarn.lock

+ 20 - 0
alias.config.js

@@ -0,0 +1,20 @@
+/**
+ * @author 梁安帝
+ * @date 2022-3-3
+ * @desc 只是为了让webstorm识别别名,无其它作用
+ * */
+
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+
+module.exports = {
+  resolve: {
+    alias: {
+      '@': resolve(__dirname, 'src'),
+      '~@': resolve(__dirname, 'src'),
+      '@views': resolve(__dirname, 'src/views'),
+      '@components': resolve(__dirname, 'src/components')
+    }
+  }
+};

+ 71 - 0
apiProxy.js

@@ -0,0 +1,71 @@
+const apiProxy = {
+    'api': {
+        local: 'http://192.168.174.70:8080/',
+        // local: 'http://8.140.240.182:18085/',
+        // local: 'http://h386t1c.nat.ipyingshe.com/seat-java/',
+        116: 'http://74.10.28.24:9999/',
+        117: 'http://74.10.28.117:9999/',
+        118: 'http://74.10.28.18:9999/',
+    },	// 席位接口代理
+    'img-api': 'http://74.10.28.118:8090/',	// 服务器静态图片代理
+    'EzServer6-api': 'http://74.10.28.116:8090/',	// 地图底图代理
+    'geoserver-api': 'http://74.10.28.39:8080/',	// 地图服务代理
+    'history-track-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.24:9999/',
+        117: 'http://74.10.28.117:9999/',
+        118: 'http://74.10.28.18:9999/',
+    },	//	历史轨迹代理
+    'rh-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.28:6990/',
+        117: 'http://74.10.28.38:8015/',
+        118: 'http://74.10.28.38:8014/',
+    },	// 融合实时船舶ws代理
+    'bd-ws-api': 'http://74.10.28.38:6991/',	// 北斗实时船舶ws代理
+    'ais-ws-api': 'http://74.10.28.28:6992/', //全球ais
+    'xmb-ws-api': 'http://74.10.28.38:6996/',	// 小目标雷达实时船舶ws代理
+    'hlx-ws-api': 'http://74.10.28.28:7000/',	// 海兰信守望者实时船舶ws代理
+    'hlx-radar-ws-api': 'http://74.10.28.28:7004/',	// 海兰信原始雷达实时船舶ws代理
+    'zyh-ws-api': 'http://74.10.28.28:6995/',	// 中远海实时船舶ws代理
+    'dc-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.24:9999/',
+        117: 'http://74.10.28.117:9999/',
+        118: 'http://74.10.28.18:9999/',
+    },	// 历史回放单船ws代理
+    'kx-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.103:8010/',
+        117: 'http://74.10.28.103:8012/',
+        118: 'http://74.10.28.103:8011/',
+    },	// 历史回放框选ws代理
+    'rh-find-ship-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.28:9000/',
+        117: 'http://74.10.28.38:9015/',
+        118: 'http://74.10.28.38:9014/',
+    },	// 融合找船代理
+    'warning-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.24:6999/',
+        117: 'http://74.10.28.117:6999/',
+        118: 'http://74.10.28.18:6999/',
+    },	//	预警推送ws代理
+    'info-center-ws-api': {
+        local:'http://74.10.28.103:8010/',
+        116: 'http://74.10.28.24:5999/',
+        117: 'http://74.10.28.117:5999/',
+        118: 'http://74.10.28.18:5999/',
+    },	//	消息中心ws代理
+    'police-man-ws-api': 'http://74.10.28.38:7003/',	//	执法记录仪ws代理
+    'speed-analysis-api': 'http://74.10.28.103:8090/',	//	轨迹列表航速分析导出
+    'jwt-ws-api': 'http://74.10.28.38:7002/',	//	警务通实时ws代理
+    'pdt-ws-api': 'http://74.10.28.38:7001/',	//	PDT实时ws代理
+    'police-car-ws-api': 'http://74.10.28.38:6999/',	//	警车实时ws代理
+    'police-boat-ws-api': 'http://74.10.28.38:6997/',	//	巡逻艇实时ws代理
+}
+
+export const getApiProxy = (key, version = null) => {
+    return version ? (apiProxy[key][version] ? apiProxy[key][version] : apiProxy[key]) : apiProxy[key]
+}

+ 28 - 0
index.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+<!--  <link rel="icon" href="/bitbug_favicon.ico" />-->
+  <meta name="referrer" content="no-referrer" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta name="keywords" content="海南省公安厅、综合态势防控平台" />
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=8"/>
+  <meta http-equiv="Pragma" content="no-cache"/>
+  <meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate"/>
+  <meta http-equiv="Cache" content="no-cache"/>
+  <meta http-equiv="expires" content="0"/>
+<!--  <script src="/gifler.js"></script>-->
+  <title><{VITE_PROJECT_TITLE}></title>
+<!--  <link rel="stylesheet" href="/Cesium/Widgets/widgets.css">-->
+<!--  <script src="/Cesium/Cesium.js"></script>-->
+  <script src="/config.js"></script>
+</head>
+
+<body style="overflow: hidden;">
+<div id="app"></div>
+<script type="module" src="/src/main.ts"></script>
+</body>
+
+</html>

+ 40 - 0
package.json

@@ -0,0 +1,40 @@
+{
+  "name": "ax-web-seats",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite --mode development --open",
+    "build": "node preBuild.js && vite build --mode development",
+    "serve": "vite preview"
+  },
+  "dependencies": {
+    "@types/node": "^20.1.0",
+    "animate.css": "^4.1.1",
+    "axios": "^1.3.4",
+    "element-plus": "^2.4.3",
+    "sass": "^1.60.0",
+    "uuid": "^9.0.0",
+    "vue": "^3.3.9",
+    "vue-router": "^4.2.5",
+    "vuex": "^4.1.0",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "fast-glob": "^3.3.0",
+    "vite-plugin-top-level-await": "^1.3.1",
+    "vite-plugin-html-env": "^1.2.8",
+    "dayjs": "^1.11.9",
+    "echarts": "^5.4.3",
+    "default-passive-events": "^2.0.0",
+    "ol": "^6.5.0",
+    "@turf/turf": "^6.5.0"
+  },
+  "devDependencies": {
+    "@types/uuid": "^9.0.2",
+    "@vitejs/plugin-vue": "^4.1.0",
+    "typescript": "^4.9.3",
+    "vite": "^4.2.0",
+    "vue-tsc": "^1.2.0",
+    "vite-plugin-compression": "^0.5.1",
+    "rollup-plugin-visualizer": "^5.9.2"
+  }
+}

+ 29 - 0
preBuild.js

@@ -0,0 +1,29 @@
+import * as child_process from 'child_process'
+import * as fs from 'fs'
+import dayjs from 'dayjs'
+
+let commitId = child_process.execSync(`git log -n1 --format=format:"%H"`).toString();
+let commitUser = child_process.execSync(`git log -n1 --format=format:"%an"`).toString();
+let commitBranch = child_process.execSync(`git log -n1 --format=format:"%d"`).toString().trim();
+let commitTime = child_process.execSync(`git log -n1 --pretty=format:"%ad" --date=iso`).toString().substring(0, 19);
+let buildUser = child_process.execSync(`git config user.name`).toString().trim();
+
+// const txt = `提交ID:${commitId}
+// 提交用户:${commitUser}
+// 提交分支:${commitBranch}
+// 提交时间:${commitTime}
+// 打包用户:${buildUser}
+// 打包时间:${dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss')}
+// `
+const j = {
+  '提交ID:': commitId,
+  '提交用户:': commitUser,
+  '提交分支:': commitBranch,
+  '提交时间:': commitTime,
+  '打包用户:': buildUser,
+  '打包时间:': dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
+}
+
+fs.writeFileSync('./src/out/git_info.json', JSON.stringify(j));
+
+console.log('========== Save git info done. ==========');

+ 38 - 0
src/App.vue

@@ -0,0 +1,38 @@
+<template>
+  <div style="overflow: hidden">
+    <ElConfigProvider :locale="locale">
+      <router-view/>
+    </ElConfigProvider>
+  </div>
+</template>
+<script lang="ts">
+import {
+  defineComponent,
+  ref,
+  nextTick,
+  onMounted,
+  watch,
+  computed,
+  ComponentInternalInstance,
+  getCurrentInstance
+} from 'vue'
+import {useStore} from 'vuex'
+import {ElConfigProvider} from 'element-plus'
+import zhLocale from 'element-plus/es/locale/lang/zh-cn'
+export default defineComponent({
+  name: 'App',
+  components: {
+    [ElConfigProvider.name]: ElConfigProvider, //添加组件
+  },
+  setup() {
+    const store = useStore()
+    const locale = ref(zhLocale)
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    return {
+      locale
+    }
+  }
+})
+</script>
+<style scope lang="scss">
+</style>

+ 19 - 0
src/api/index.ts

@@ -0,0 +1,19 @@
+import HttpService from "./request";
+
+export const handle = (p: { [index: string]: any}) => {
+  const httpService = new HttpService()
+  // @ts-ignore
+  return httpService[p.method.toLowerCase()](p)
+}
+
+// @ts-ignore
+const files = import.meta.glob("/src/api/modules/**/*.ts")
+const apiModule: any = {}
+
+for (const [key, value] of Object.entries(files)) {
+  // @ts-ignore
+  value().then(res => {
+    Object.assign(apiModule, res)
+  })
+}
+export default apiModule

+ 62 - 0
src/api/interceptors.ts

@@ -0,0 +1,62 @@
+import axios from 'axios';
+import {toLogin} from "@/utils/permissions";
+import {ElMessage} from "element-plus";
+export class Interceptors {
+  public instance: any
+
+  constructor() {
+    this.instance = axios.create({timeout: 1000 * 300})
+    this.initInterceptors()
+  }
+
+  public getInterceptors() {
+    return this.instance
+  }
+
+  public initInterceptors() {
+    // this.instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
+    /**
+     * 请求拦截器
+     * 每次请求前,如果存在token则在请求头中携带token
+     */
+    this.instance.interceptors.request.use(
+      (config: any) => {
+        // if (!config.headers.Authorization) {
+        //   const token = sessionStorage.getItem('ax_token');
+        //   if (token) {
+        //     config.headers.Authorization = token;
+        //   } else {
+        //     // @ts-ignore
+        //     delete config.headers.Authorization
+        //   }
+        // }
+        return config;
+      },
+      (error: any) => {
+      }
+    );
+
+    // 响应拦截器
+    this.instance.interceptors.response.use(
+      // 请求成功
+      (res: any) => {
+        if (res.status === 200) {
+          //
+          if (res.data?.code === 402) {
+            ElMessage.warning(res.data.message)
+            sessionStorage.removeItem('sg_token')
+            toLogin()
+          } else {
+            return res
+          }
+        }
+      },
+      // 请求失败
+      (error: any) => {
+        ElMessage.error(error)
+        console.error('错误接口:' + error.config.url)
+        // 对响应错误做点什么
+        return Promise.reject(error)
+      });
+  }
+}

+ 15 - 0
src/api/modules/account.ts

@@ -0,0 +1,15 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+// 获取个人信息
+export const getUserInfo = () => handle({
+  url: `/${suffix}/account/search`,
+  method: 'get',
+})
+//  根据部门ID获取用户列表
+export const getAccountListByDeptId = (params: any) => handle({
+  url: `/${suffix}/account/list`,
+  method: 'get',
+  params
+})

+ 14 - 0
src/api/modules/auth.ts

@@ -0,0 +1,14 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+export const login = (params: any) => handle({
+  url: `/${suffix}/auth/login`,
+  method: 'post',
+  params
+})
+
+export const logout = () => handle({
+  url: `/${suffix}/auth/logout`,
+  method: 'get',
+})

+ 16 - 0
src/api/modules/common.ts

@@ -0,0 +1,16 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+// 文件上传
+export const commonUpload = (params: any) => handle({
+  url: `/${suffix}/common/upload`,
+  method: 'post',
+  params,
+})
+// 文件下载
+export const commonDownload = (params: any) => handle({
+  url: `/${suffix}/common/download`,
+  method: 'get',
+  params,
+  config: {responseType: 'blob'}
+})

+ 22 - 0
src/api/modules/config.ts

@@ -0,0 +1,22 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+//  公共配置 > 获取对应详情
+// 签到管理-签到时间	sign.begin.time
+// 签到管理-签到提醒时间	sign.remind.time
+// 签到管理-签退时间	sign.end.time
+// 日志管理-截止提交时间	log.submit.time
+// 日志管理-日志提交提醒时间	log.remind.time
+// 周报管理-截止提交时间	week.submit.time
+// 周报管理-日志提交提醒时间	week.remind.time
+export const getConfigConfigKey = (key: any) => handle({
+  url: `/${suffix}/config/configKey/${key}`,
+  method: 'get',
+})
+//  公共配置 > 修改键值
+export const editConfigEditConfigKey = (params: any) => handle({
+  url: `/${suffix}/config/edit/configKey`,
+  method: 'put',
+  params
+})

+ 22 - 0
src/api/modules/daily.ts

@@ -0,0 +1,22 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+//  日志管理 > 列表
+export const getDailyReportList = (params: any) => handle({
+  url: `/${suffix}/daily/report/list`,
+  method: 'post',
+  params
+})
+//  日志管理 > 新增
+export const addDailyReportSave = (params: any) => handle({
+  url: `/${suffix}/daily/report/save`,
+  method: 'post',
+  params
+})
+//  日志管理 > 编辑
+export const editDailyReportEdit = (params: any) => handle({
+  url: `/${suffix}/daily/report/edit`,
+  method: 'put',
+  params
+})

+ 8 - 0
src/api/modules/dept.ts

@@ -0,0 +1,8 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+export const getDeptList = () => handle({
+  url: `/${suffix}/dept/list`,
+  method: 'get',
+})

+ 36 - 0
src/api/modules/dict.ts

@@ -0,0 +1,36 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+//  字典管理 > 树
+export const getDictTypeTreeSelectTree = () => handle({
+  url: `/${suffix}/dict/type/treeselect`,
+  method: 'get',
+})
+//  字典管理 > 列表
+export const getDictDataList = (params: any) => handle({
+  url: `/${suffix}/dict/data/list`,
+  method: 'get',
+  params
+})
+//  字典管理 > 列表 > 新增
+export const addDictData = (params: any) => handle({
+  url: `/${suffix}/dict/data/save`,
+  method: 'post',
+  params
+})
+//  字典管理 > 列表 > 编辑
+export const editDictData = (params: any) => handle({
+  url: `/${suffix}/dict/data/edit`,
+  method: 'put',
+  params
+})
+//  字典管理 > 列表 > 删除
+export const delDictData = (id: any) => handle({
+  url: `/${suffix}/dict/data/${id}`,
+  method: 'delete',
+})
+//  字典管理 > 获取字典翻译
+export const dictDataType = (type: any) => handle({
+  url: `/${suffix}/dict/data/type/${type}`,
+  method: 'get',
+})

+ 17 - 0
src/api/modules/login-log.ts

@@ -0,0 +1,17 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+//  登录日志 > 列表
+export const getLoginLogList = (params: any) => handle({
+  url: `/${suffix}/login/log/list`,
+  method: 'post',
+  params
+})
+//  登录日志 > 导出
+export const loginLogExport = (params: any) => handle({
+  url: `/${suffix}/login/log/export`,
+  method: 'post',
+  params,
+  config: {responseType: 'blob'}
+})

+ 40 - 0
src/api/modules/notice.ts

@@ -0,0 +1,40 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+//  通知公告 > 列表
+export const getNoticeList = (params: any) => handle({
+  url: `/${suffix}/notice/list`,
+  method: 'post',
+  params
+})
+//  通知公告 > 新增
+export const addNoticeSave = (params: any) => handle({
+  url: `/${suffix}/notice/save`,
+  method: 'post',
+  params
+})
+//  通知公告 > 编辑
+export const editNoticeEdit = (params: any) => handle({
+  url: `/${suffix}/notice/edit`,
+  method: 'put',
+  params
+})
+//  通知公告 > 查看
+export const getNoticeSearch = (params: any) => handle({
+  url: `/${suffix}/notice/search`,
+  method: 'get',
+  params
+})
+//  通知公告 > 发布
+export const getNoticePublish = (params: any) => handle({
+  url: `/${suffix}/notice/publish`,
+  method: 'get',
+  params
+})
+//  通知公告 > 撤回
+export const getNoticeRevoke = (params: any) => handle({
+  url: `/${suffix}/notice/revoke`,
+  method: 'get',
+  params
+})

+ 17 - 0
src/api/modules/oper-log.ts

@@ -0,0 +1,17 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+//  操作记录 > 列表
+export const getOperationLogList = (params: any) => handle({
+  url: `/${suffix}/operLog/list`,
+  method: 'post',
+  params
+})
+//  操作记录 > 导出
+export const operLogExport = (params: any) => handle({
+  url: `/${suffix}/operLog/export`,
+  method: 'post',
+  params,
+  config: {responseType: 'blob'}
+})

+ 8 - 0
src/api/modules/sign.ts

@@ -0,0 +1,8 @@
+import { handle } from '../index'
+
+const suffix = 'api'
+
+export const getSignTime = () => handle({
+  url: `/${suffix}/sign/time`,
+  method: 'get',
+})

+ 32 - 0
src/api/modules/ztpt.ts

@@ -0,0 +1,32 @@
+import { handle } from '../index'
+
+const suffix = 'api-ztpt'
+
+export const ztptGetLoginCode = (params: any) => handle({
+  url: `/${suffix}/api-idaas/validata/code/${params}`,
+  method: 'get',
+  config: {
+    responseType: 'blob'
+  }
+})
+
+export const ztptLogin = (params: any) => handle({
+  url: `/${suffix}/api-idaas/idaas/sso/login`,
+  method: 'post',
+  params
+})
+
+export const ztptGetUserInfo = () => handle({
+  url: `/${suffix}/api-idaas/idaas/sso/user/info`,
+  method: 'get',
+})
+
+export const ztptGetUserRoleList = () => handle({
+  url: `/${suffix}/api-idaas/idaas/ps/user/roleList`,
+  method: 'get',
+})
+
+export const ztptLogout = () => handle({
+  url: `/${suffix}/api-idaas/mq/user/logout`,
+  method: 'get',
+})

+ 129 - 0
src/api/request.ts

@@ -0,0 +1,129 @@
+import { Interceptors } from './interceptors';
+import { ElMessage } from "element-plus";
+import {downloadFile} from "@/utils/util";
+export class HttpRequest {
+  public axios: any
+  constructor() {
+    // 获取axios实例
+    this.axios = new Interceptors().getInterceptors();
+  }
+  public get = ({url, params, config = {}}: { [index: string]: any }) => {
+    return new Promise((resolve, reject) => {
+      let paramUrl = url
+      if (params) {
+        if (typeof params === 'object') {
+          let str = ''
+          Object.entries(params).map(([k, v]: any, i) => {
+            if (i > 0) {
+              str += '&'
+            }
+            str += `${k}=${encodeURIComponent(v)}`
+          })
+          paramUrl += `?${str}`
+        } else {
+          paramUrl += `?${params}`
+        }
+      }
+      this.axios.get(paramUrl, {
+        headers: {
+          Authorization: sessionStorage.getItem('sg_token')
+        },
+        ...config
+      }).then((res: any) => {
+        resolve(res.data)
+      }).catch((err) => {
+        reject(err);
+      })
+    })
+  }
+  public post = ({url, params, config = {}}: { [index: string]: any }) => {
+    return new Promise((resolve, reject) => {
+      this.axios.post(url, params, {
+        headers: {
+          Authorization: sessionStorage.getItem('sg_token'),
+        },
+        ...config
+      }).then((res: any) => {
+        resolve(res.data)
+      }).catch((err) => {
+        reject(err);
+      })
+    })
+  }
+  public put = ({url, params, config = {}}: { [index: string]: any }) => {
+    return new Promise((resolve, reject) => {
+      this.axios.put(url, params, {
+        headers: {
+          Authorization: sessionStorage.getItem('sg_token'),
+        },
+        ...config
+      }).then((res: any) => {
+        resolve(res.data)
+      }).catch((err) => {
+        reject(err);
+      })
+    })
+  }
+  public delete = ({url, params, config = {}}: { [index: string]: any }) => {
+    return new Promise((resolve, reject) => {
+      let paramUrl = url
+      if (params) {
+        if (typeof params === 'object') {
+          let str = ''
+          Object.entries(params).map(([k, v]: any, i) => {
+            if (i > 0) {
+              str += '&'
+            }
+            str += `${k}=${encodeURIComponent(v)}`
+          })
+          paramUrl += `?${str}`
+        } else {
+          paramUrl += `?${params}`
+        }
+      }
+      this.axios.delete(paramUrl, {
+        headers: {
+          Authorization: sessionStorage.getItem('sg_token')
+        },
+        ...config
+      }).then((res: any) => {
+        resolve(res.data)
+      }).catch((err) => {
+        reject(err);
+      })
+    })
+  }
+  public download = ({url, params, fileName, config = {}}: { [index: string]: any }) => {
+    return new Promise((resolve, reject) => {
+      let paramUrl = url
+      if (params) {
+        if (typeof params === 'object') {
+          let str = ''
+          Object.entries(params).map(([k, v]: any, i) => {
+            if (i > 0) {
+              str += '&'
+            }
+            str += `${k}=${encodeURIComponent(v)}`
+          })
+          paramUrl += `?${str}`
+        } else {
+          paramUrl += `?${params}`
+        }
+      }
+      this.axios.get(paramUrl, {
+        headers: {
+          Authorization: sessionStorage.getItem('sg_token'),
+        },
+        responseType: 'arraybuffer',
+        ...config
+      }).then((res: any) => {
+        downloadFile(res, fileName)
+        resolve(res)
+      }).catch((err) => {
+        reject(err);
+      })
+    })
+  }
+}
+
+export default HttpRequest

BIN
src/assets/images/cus/cus-tab_type2-active.png


BIN
src/assets/images/cus/cus-title_type2-icon.png


BIN
src/assets/images/cus/file-del.png


BIN
src/assets/images/cus/to-bottom.png


BIN
src/assets/images/cus/to-right.png


BIN
src/assets/images/file-type/file-type_default.png


BIN
src/assets/images/file-type/file-type_excel.png


BIN
src/assets/images/file-type/file-type_pdf.png


BIN
src/assets/images/file-type/file-type_ppt.png


BIN
src/assets/images/file-type/file-type_rar.png


BIN
src/assets/images/file-type/file-type_txt.png


BIN
src/assets/images/file-type/file-type_word.png


BIN
src/assets/images/global/login_bg.jpeg


BIN
src/assets/images/global/password.png


BIN
src/assets/images/global/temp.png


BIN
src/assets/images/global/user.png


BIN
src/assets/images/layout/background.png


BIN
src/assets/images/layout/background_home.png


BIN
src/assets/images/layout/head-bg.png


BIN
src/assets/images/layout/head-com-avatar.png


BIN
src/assets/images/layout/head-com-bg.png


BIN
src/assets/images/layout/head-com-dropdown.png


BIN
src/assets/images/layout/head-com-info-center.png


BIN
src/assets/images/layout/head-com-search.png


BIN
src/assets/images/layout/logo_icon.png


BIN
src/assets/images/layout/logo_name.png


BIN
src/assets/images/layout/menu-active-special.png


BIN
src/assets/images/layout/menu-bg-special.png


BIN
src/assets/images/layout/menu-item-bg-active.png


BIN
src/assets/images/layout/menu-item-bg.png


BIN
src/assets/images/layout/menu-top-title.png


BIN
src/assets/mock.png


File diff suppressed because it is too large
+ 6 - 0
src/assets/svg/business/add.svg


File diff suppressed because it is too large
+ 5 - 0
src/assets/svg/business/arrow_1.svg


File diff suppressed because it is too large
+ 8 - 0
src/assets/svg/business/arrow_2.svg


+ 7 - 0
src/assets/svg/business/close_1.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="1692149440475" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1471"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+  <path d="M250.785684 841.155368l589.473684-589.473684-67.907368-67.907368-589.473684 589.473684z"></path>
+  <path d="M772.358737 841.155368l67.907368-67.907368-589.473684-589.473684-67.907368 67.907368z"></path>
+</svg>

File diff suppressed because it is too large
+ 8 - 0
src/assets/svg/business/close_2.svg


+ 11 - 0
src/assets/svg/business/close_3.svg

@@ -0,0 +1,11 @@
+<?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="1699925907543" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4007"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+  <path
+    d="M512 1024C229.376 1024 0 794.624 0 512S229.376 0 512 0s512 229.376 512 512-229.376 512-512 512z m0-975.36C257.024 48.64 48.64 257.024 48.64 512c0 254.976 207.872 463.36 463.36 463.36S975.36 767.488 975.36 512 766.976 48.64 512 48.64z"
+    p-id="4008"></path>
+  <path
+    d="M548.864 512l195.072-195.072c9.728-9.728 9.728-25.6 0-36.864l-1.536-1.536c-9.728-9.728-25.6-9.728-35.328 0L512 475.136 316.928 280.064c-9.728-9.728-25.6-9.728-35.328 0l-1.536 1.536c-9.728 9.728-9.728 25.6 0 36.864L475.136 512 280.064 707.072c-9.728 9.728-9.728 25.6 0 36.864l1.536 1.536c9.728 9.728 25.6 9.728 35.328 0L512 548.864l195.072 195.072c9.728 9.728 25.6 9.728 35.328 0l1.536-1.536c9.728-9.728 9.728-25.6 0-36.864L548.864 512z"
+    p-id="4009"></path>
+</svg>

+ 7 - 0
src/assets/svg/business/cus-content_field-column-drag.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="1692757700168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="17105" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
+  <path d="M204.8 341.333333h614.4v68.266667H204.8v-68.266667z m0 273.066667h614.4v68.266667H204.8v-68.266667z"
+        fill="#000000" p-id="17106"></path>
+</svg>

+ 7 - 0
src/assets/svg/business/cus-content_field-column-select.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="1692757700168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="17105" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
+  <path d="M204.8 341.333333h614.4v68.266667H204.8v-68.266667z m0 273.066667h614.4v68.266667H204.8v-68.266667z"
+        p-id="17106"></path>
+</svg>

File diff suppressed because it is too large
+ 8 - 0
src/assets/svg/business/cus-content_field-column.svg


File diff suppressed because it is too large
+ 11 - 0
src/assets/svg/business/cus-content_field-fixed.svg


File diff suppressed because it is too large
+ 11 - 0
src/assets/svg/business/cus-content_field-in.svg


File diff suppressed because it is too large
+ 11 - 0
src/assets/svg/business/cus-content_tools.svg


+ 9 - 0
src/assets/svg/business/default.svg

@@ -0,0 +1,9 @@
+<?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="1692585246601" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
+     p-id="10280" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+  <path
+    d="M512 958.8C265.6 958.8 65.2 758.4 65.2 512S265.6 65.2 512 65.2 958.8 265.6 958.8 512 758.4 958.8 512 958.8z m0-832c-212.4 0-385.2 172.8-385.2 385.2S299.6 897.2 512 897.2 897.2 724.4 897.2 512 724.4 126.8 512 126.8z"
+    p-id="10281"></path>
+  <path d="M512 512m-169.5 0a169.5 169.5 0 1 0 339 0 169.5 169.5 0 1 0-339 0Z" p-id="10282"></path>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/business/menu-mock.svg


+ 8 - 0
src/assets/svg/business/search.svg

@@ -0,0 +1,8 @@
+<?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="1692862188228" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1695"
+     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
+  <path
+    d="M469.333333 85.333333a384 384 0 0 1 300.032 623.658667l139.306667 139.349333a42.666667 42.666667 0 1 1-60.330667 60.330667l-139.349333-139.306667A384 384 0 1 1 469.333333 85.333333z m0 85.333334a298.666667 298.666667 0 1 0 0 597.333333 298.666667 298.666667 0 0 0 0-597.333333z"
+    p-id="1696"></path>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/business/to_bottom_camera.svg


File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/business/to_right_camera.svg


File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/business/u212.svg


+ 17 - 0
src/assets/svg/global/关注对象.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<path d="M99.8,117.4l-9.9,5.2c-1.2,0.6-2.6-0.4-2.4-1.7l1.9-11l-8-7.8c-1-0.9-0.4-2.6,0.9-2.8l11.1-1.6l5-10c0.6-1.2,2.3-1.2,2.9,0
+	l5,10l11.1,1.6c1.3,0.2,1.9,1.8,0.9,2.8l-8,7.8l1.9,11c0.2,1.3-1.2,2.4-2.4,1.7L99.8,117.4L99.8,117.4z"/>
+<g>
+	<path d="M36.4,47.2h4.5c-0.3,1.5-0.5,3.1-0.5,4.9c0,15.8,12.9,28.7,28.7,28.7c15.8,0,28.7-12.9,28.7-28.7c0-1.8-0.2-3.4-0.5-4.9
+		l4.1,0c1.4-0.1,3-1.7,3-2.9C103.5,24.4,90.9,4.3,68.2,4.3c-22.7,0-34.6,19.8-36,38.2c-0.1,1.1,0.8,2.4,1.5,3.3
+		C34.3,46.3,35.3,47.2,36.4,47.2z M70.4,34.2l-4.1,0l-5.3,0.2c-6.9,0.5-11.6,1.9-14.9,4.3h-4.9c2.7-15.8,13.3-26,27-26h0.5
+		c13.5,0.2,23.9,10.4,26.6,26H92c-3.2-2.4-7.7-3.7-14.2-4.3L77,34.4L70.4,34.2z M52.6,44.7c0.8-0.4,1.6-0.7,2.5-0.9l0.5-0.1
+		c1-0.2,2.2-0.4,3.4-0.6l1.7-0.2l7-0.3h1.5l5,0.1l4.2,0.3l2.8,0.4c0.5,0.1,0.9,0.2,1.3,0.3l0.5,0.1c0.9,0.2,1.9,0.6,2.7,1
+		c2.3,1.1,3.7,2.7,3.7,7.4c0,11.2-9.1,20.2-20.2,20.2c-11.2,0-20.2-9.1-20.2-20.2C48.8,47.3,50.2,45.8,52.6,44.7z"/>
+	<path d="M76.7,117.2c0-0.6,0-1.2,0-1.7H20c1.7-13.7,13.2-23.9,27.1-23.9h36.7c1.8-3,4-5.9,6.4-8.4c-0.3,0-0.6,0-0.9,0H47.1
+		c-9.5,0-18.5,3.7-25.3,10.5s-10.5,15.7-10.5,25.3v5h65.7C76.8,121.7,76.7,119.5,76.7,117.2z"/>
+</g>
+</svg>

+ 11 - 0
src/assets/svg/global/字典管理.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<path d="M97.3,9.4H30.7c-8.6,0-15.6,7-15.6,15.6v83c0,6.7,5.5,12.2,12.2,12.2h75.1c2.3,0,4.2-1.9,4.2-4.2s-1.9-4.2-4.2-4.2H27.2
+	c-2,0-3.7-1.6-3.7-3.7s1.6-3.7,3.7-3.7h81.4c2.3,0,4.2-1.9,4.2-4.2V25C112.9,16.4,105.9,9.4,97.3,9.4z M23.6,25
+	c0-3.9,3.2-7.1,7.1-7.1h37.2v26.8c0,1.7,1,3.3,2.6,3.9c1.6,0.7,3.4,0.3,4.6-0.9l7.1-7.1l7.1,7.1c1.2,1.2,3,1.6,4.6,0.9
+	c1.6-0.7,2.6-2.2,2.6-3.9V17.9h0.7c3.9,0,7.1,3.2,7.1,7.1v70.8H37.7V44.7c0-2.3-1.9-4.2-4.2-4.2s-4.2,1.9-4.2,4.2v51.1h-2
+	c-1.3,0-2.5,0.2-3.7,0.6V25z M88.1,34.4l-2.9-2.9c-0.8-0.8-1.9-1.3-3-1.3c0,0,0,0,0,0c-1.1,0-2.2,0.5-3,1.3l-2.9,2.9V17.9h11.8V34.4
+	z"/>
+</svg>

+ 16 - 0
src/assets/svg/global/安全审计.svg

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 109.8 109.8" style="enable-background:new 0 0 109.8 109.8;" xml:space="preserve">
+<path d="M53,109.6c-4.3,0-8.6-1.3-12.2-3.7l-15.4-10C11.6,86.8,3.3,71.3,3.3,54.8v-22c0-4.8,3.3-9,8-10.1c12.8-3.4,24.7-9.5,35-17.8
+	c3.9-3.3,9.5-3.3,13.4,0c10.3,8.3,22.2,14.4,35,17.8c4.7,1.1,8.1,5.3,8.1,10.2v8.5c0,2.2-1.8,4-4,4s-4-1.8-4-4l0,0v-8.6
+	c0-1-0.6-1.9-1.6-2.3C79,27,65.9,20.4,54.5,11.3c-0.8-0.7-2-0.7-2.9,0C40.2,20.4,27,27,12.9,30.6c-1.1,0.3-1.8,1.3-1.7,2.3v21.9
+	c0,13.8,6.9,26.8,18.5,34.4l15.4,10.1c4.7,3.1,10.8,3.1,15.6,0c1.9-1.1,4.4-0.5,5.5,1.4c1,1.8,0.6,4-1.1,5.2
+	C61.5,108.3,57.3,109.6,53,109.6z"/>
+<path d="M81.4,54.8c0.3-0.1,0.6-0.1,0.9,0l15.4,8.9c0.5,0.3,0.9,0.9,0.9,1.5v17.7c0,0.6-0.3,1.2-0.9,1.5l-15.4,8.9
+	c-0.5,0.3-1.2,0.3-1.7,0l-15.4-8.9c-0.5-0.3-0.8-0.9-0.8-1.5V65.4c0-0.6,0.3-1.2,0.9-1.5L80.7,55c0.3-0.1,0.6-0.1,0.9,0 M81.4,47
+	c-1.7,0-3.4,0.5-4.9,1.3l-15.3,8.6c-3,1.7-4.9,4.9-4.9,8.4v17.6c0,3.5,1.9,6.7,4.9,8.4l15.4,8.9c3,1.7,6.7,1.7,9.8,0l15.4-8.9
+	c3-1.8,4.8-5,4.8-8.4V65.4c0-3.5-1.9-6.7-4.9-8.4l-15.2-8.9c-1.5-0.9-3.2-1.3-4.9-1.3L81.4,47z"/>
+<path d="M82,72c1.2,0,2.2,1,2.2,2.2s-1,2.2-2.2,2.2s-2.2-1-2.2-2.2l0,0C79.8,73,80.8,72,82,72 M82,66.3c-4.4,0-7.9,3.5-7.9,7.9
+	s3.5,7.9,7.9,7.9s7.9-3.5,7.9-7.9c0.1-4.4-3.3-8-7.7-8.2c-0.1,0-0.2,0-0.2,0S82,66.3,82,66.3z"/>
+</svg>

+ 13 - 0
src/assets/svg/global/权限管理.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<g>
+	<path d="M42.7,53.8c0,10.5,7.8,19.6,18.2,21.2v23.4c0,2.2,1.9,4,4,4s4-1.9,4-4V86h5.4c2.2,0,4-1.9,4-4.1c0-2.2-1.9-4-4-4H69v-3.1
+		c9.7-2.1,16.8-10.9,16.8-21c0-11.9-9.7-21.5-21.5-21.5S42.7,41.9,42.7,53.8z M50.8,53.8c0-7.5,5.9-13.4,13.5-13.4
+		c7.5,0,13.4,5.9,13.4,13.4c0,7.5-5.9,13.4-13.4,13.4c0,0,0,0,0,0c-3.6,0-7-1.4-9.5-3.9C52.2,60.8,50.8,57.4,50.8,53.8z"/>
+	<path d="M111.8,26.5c-0.3-1.7-1.4-3.2-2.9-3.7L65.1,8.6c-0.8-0.3-1.6-0.3-2.4,0L18.9,22.8c-1.6,0.5-2.7,2.1-2.7,3.8l0,55.4
+		c0.4,3.1,4.4,19.4,46.1,38.9c0.6,0.3,1.1,0.3,1.6,0.3c0.5,0,1.1,0,1.6-0.3c41.7-19.6,45.8-35.9,46.1-39L111.8,26.5z M103.4,29.6
+		v51.8c-0.2,0.8-4.1,14.3-39.7,31.4C27.3,95.3,24.2,81.9,24.1,81.4V29.6l39.7-13.1L103.4,29.6z"/>
+</g>
+</svg>

+ 11 - 0
src/assets/svg/global/标签管理.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<path d="M71.6,127.9c-2.7,0-5.3-1.1-7.2-3L3.9,61.8c-1.8-1.9-3.5-4.2,6.2-44.2c1.1-4.8,4.9-8.4,9.8-9.3c39.8-6.7,41.6-5.2,43.4-3.3
+	l60.4,63.2c3.9,4.9,3.2,12-1.5,16l-41.6,40C78.2,126.6,75,127.9,71.6,127.9z M10.6,56.9l59.6,62.4c0.4,0.3,0.9,0.5,1.4,0.5
+	c1.3-0.1,2.5-0.6,3.4-1.5l41.5-40c1.7-1.6,2.1-3.8,1.3-4.6L58.1,11.5c-12.4,0.7-24.7,2.3-36.8,5c-1.6,0.3-2.9,1.5-3.3,3.1
+	C14.6,31.8,12.1,44.3,10.6,56.9z"/>
+<path d="M44.3,56.5c-7.7,0-13.9-6.2-13.9-13.9s6.2-13.9,13.9-13.9s13.9,6.2,13.9,13.9c0,0,0,0.1,0,0.1C58.2,50.3,52,56.5,44.3,56.5z
+	 M44.3,37c-3.1,0-5.7,2.5-5.7,5.7s2.5,5.7,5.7,5.7s5.7-2.5,5.7-5.7S47.5,37,44.3,37L44.3,37z"/>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/global/水军水手.svg


+ 23 - 0
src/assets/svg/global/港岙口.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<g>
+	<path d="M124.5,98.9l0-0.4c-0.2-2.1-2-3.7-4.2-3.7h-3.1l-9.3-56.7h15.5v-8.4h-13.4l0-15.1l0-0.4c0-0.2-0.1-0.4-0.1-0.5
+		c-0.6-2.2-2.8-3.6-5.1-3L28.1,29.7h-1.3v8.4h3.6v7.5l-0.8,0C23.6,46,18.9,51,18.9,57l0,7.3l0,0.4c0.2,2.1,2,3.7,4.2,3.7l21.7,0
+		l0.4,0c2.1-0.2,3.7-2,3.7-4.2l0-7.3l0-0.6c-0.3-5.6-4.6-10.1-10.1-10.7v-7.6h44.1l-9.3,56.7l-4,0l-0.4,0c-2.1,0.2-3.7,2-3.7,4.2
+		l0,16.4l0,0.4c0.2,2.1,2,3.7,4.2,3.7l50.7,0l0.4,0c2.1-0.2,3.7-2,3.7-4.2L124.5,98.9z M40.5,57l0,3H27.2l0-3l0-0.4
+		c0.2-1.6,1.5-2.7,3-2.7h0h7.2l0.6,0.1C39.5,54.3,40.5,55.6,40.5,57z M62.6,29.7l38.9-9.7l0,9.7H62.6z M99.1,57.6l3.3-1.3l0.5,3.2
+		L99.1,57.6z M95.3,78.6l9.8-5.3l1.7,10.6L95.3,78.6z M85.4,74.5l2.2-13.4l11.6,6L85.4,74.5z M84.1,82.7l24.5,11.2l0.1,0.9H82.1
+		L84.1,82.7z M101,47.9L89,52.5l2.4-14.4h8L101,47.9z M116.1,103.1v8H73.8v-8H116.1z"/>
+	<path d="M60.9,103.8l-1.5,0.1c-2.1,0.2-3.9,1.2-5.2,2.9c-0.3,0.3-0.7,0.5-1.2,0.5c0,0,0,0,0,0c-0.5,0-1-0.2-1.4-0.7
+		c-0.2-0.2-0.4-0.5-0.6-0.7l-0.2-0.2l5.5-12.3l0.2-0.4c0.1-0.4,0.2-0.8,0.2-1.2c0-2.3-1.9-4.2-4.2-4.2l-41.1,0l-0.4,0
+		c-0.4,0-0.9,0.2-1.3,0.3c-1,0.5-1.8,1.3-2.2,2.3c-0.4,1-0.4,2.2,0.1,3.2l5.9,12.9c-0.1,0.1-0.2,0.2-0.2,0.3
+		c-0.3,0.4-0.8,0.6-1.2,0.5c-0.5,0-1-0.3-1.4-0.7c-1.3-1.7-3.5-2.8-5.7-2.8H4.1v6.6l-0.5,0l0.5,1.2l0.2,0.2c2,2.2,4.6,3.5,7.4,3.7
+		c0.1,0,0.3,0,0.4,0c2.3,0,4.6-0.8,6.4-2.3c2.1,1.7,4.5,2.5,7.1,2.3c2.5,0.1,5-0.7,7-2.3c2.1,1.7,4.6,2.5,7.1,2.3
+		c2.6,0.2,5-0.7,7-2.3c2,1.6,4.4,2.4,6.9,2.3c2.7-0.1,5.2-1.3,7.1-3.5c0.2-0.2,0.5-0.6,0.3-1V103.8z M32.5,103.8
+		c-2.5-0.2-5,0.9-6.5,2.9c-0.2,0.2-0.4,0.3-0.6,0.4c-0.3-0.1-0.5-0.3-0.8-0.5c-0.2-0.2-0.4-0.5-0.6-0.7l-0.4-0.4
+		c-0.6-0.5-1.3-0.9-2-1.2l-3.8-8.4H46l-4.1,9.1c-0.7,0.5-1.3,1-1.8,1.6c-0.2,0.2-0.4,0.3-0.6,0.4c-0.3-0.1-0.5-0.3-0.7-0.5
+		c-0.2-0.2-0.4-0.5-0.6-0.7l-0.4-0.4C36.3,104.3,34.4,103.7,32.5,103.8z"/>
+</g>
+</svg>

+ 13 - 0
src/assets/svg/global/用户管理.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<g>
+	<path d="M68.7,97.9c0,2.3,1.8,4.2,4.2,4.2h31.1c2.3,0,4.2-1.8,4.2-4.2s-1.8-4.2-4.2-4.2H72.9C70.6,93.7,68.7,95.5,68.7,97.9z"/>
+	<path d="M87.5,78c1.1,0,2.2-0.4,3-1.2c0.8-0.8,1.3-1.8,1.3-3c0-1.1-0.4-2.2-1.2-3c-5.5-5.7-11.8-9.8-18.8-12.4
+		c7.2-5.3,11.5-13.5,11.5-22.5c0-15.4-12.5-27.9-27.9-27.9C40.1,8.1,27.6,20.6,27.6,36c0,8.9,4.4,17.3,11.6,22.5
+		C18.3,66.2,3.9,88.4,3.9,113c0,2.3,1.8,4.2,4.2,4.2s4.2-1.8,4.2-4.2c0-27.1,19.4-49.2,43.3-49.2c10.8,0,21.1,4.6,29.1,12.8
+		C85.3,77.5,86.4,78,87.5,78z M75,36c0,10.8-8.8,19.5-19.5,19.5S36,46.8,36,36s8.8-19.5,19.5-19.5S75,25.3,75,36z"/>
+	<path d="M122.7,109.4H72.9c-2.3,0-4.2,1.8-4.2,4.2s1.8,4.2,4.2,4.2h49.8c2.3,0,4.2-1.8,4.2-4.2S125.1,109.4,122.7,109.4z"/>
+</g>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/global/网络管理.svg


+ 13 - 0
src/assets/svg/global/船舶管理.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 144.1 128" style="enable-background:new 0 0 144.1 128;" xml:space="preserve">
+<path d="M44.2,50.3h17.5c2.4,0,4.4,2,4.4,4.4l0,0c0,2.4-2,4.4-4.4,4.4H44.2c-2.4,0-4.4-2-4.4-4.4l0,0C39.8,52.3,41.8,50.3,44.2,50.3
+	z"/>
+<path d="M110.5,116.6l26.2-40.7c1.9-2.9-0.2-6.7-3.7-6.7h-21.9V35.6c0-2.4-2-4.4-4.4-4.4H70.2V15.3c0-2.4-2-4.4-4.4-4.4h-26
+	c-2.4,0-4.4,2-4.4,4.4v16H25.1c-2.4,0-4.4,2-4.4,4.4v33.6H10.5c-2.9,0-5,2.8-4.2,5.6l11.8,40.6c0.5,1.9,2.2,3.2,4.2,3.2h84.5
+	C108.2,118.6,109.7,117.9,110.5,116.6z M48.6,19.7h8.6c2.4,0,4.4,2,4.4,4.4v7.3H44.2V24C44.2,21.6,46.1,19.7,48.6,19.7z M34,40h63.9
+	c2.4,0,4.4,2,4.4,4.4v5.9H82c-2.4,0-4.4,2-4.4,4.4l0,0c0,2.4,2,4.4,4.4,4.4h20.4v10.2H29.6V44.4C29.6,42,31.6,40,34,40z M120.7,84.7
+	l-15,23.3c-0.8,1.2-2.2,2-3.7,2H29.1c-2,0-3.7-1.3-4.2-3.2l-6.4-23.3c-0.8-2.8,1.3-5.5,4.2-5.5h94.2
+	C120.3,77.9,122.5,81.7,120.7,84.7z"/>
+</svg>

File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/global/菜单管理.svg


File diff suppressed because it is too large
+ 1 - 0
src/assets/svg/global/调度管理.svg


+ 12 - 0
src/assets/svg/global/部门管理.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<path d="M118.7,83.2h-7.8L95,62c-0.7-1-1.7-1.5-2.9-1.5H69.1V47.9h28.3c5,0,9-4,9-9V28.2c0-5-4-9-9-9h-64c-5,0-9,4-9,9v10.7
+	c0,5,4,9,9,9h28.3v12.7h-23c-1.2,0-2.3,0.5-3,1.5L19.9,83.2h-7.8c-2.1,0-3.7,1.6-3.7,3.7v21.3c0,2.1,1.6,3.7,3.7,3.7h21.3
+	c2.1,0,3.7-1.6,3.7-3.7V86.9c0-2.1-1.6-3.7-3.7-3.7h-4.3l11.5-15.3h21.2v15.3h-7c-2.1,0-3.7,1.6-3.7,3.7v21.3c0,2.1,1.6,3.7,3.7,3.7
+	h21.3c2.1,0,3.7-1.6,3.7-3.7V86.9c0-2.1-1.6-3.7-3.7-3.7h-7V67.9h21.1l11.6,15.3h-4.3c-2.1,0-3.7,1.6-3.7,3.7v21.3
+	c0,2.1,1.6,3.7,3.7,3.7h21.3c2.1,0,3.7-1.6,3.7-3.7V86.9C122.4,84.8,120.8,83.2,118.7,83.2z M101.1,104.5v-14h14v14H101.1z
+	 M72.4,90.5v14h-14v-14H72.4z M99.1,28.2v10.7c0,0.9-0.7,1.7-1.7,1.7h-64c-0.9,0-1.7-0.7-1.7-1.7V28.2c0-0.9,0.7-1.7,1.7-1.7h64
+	C98.3,26.5,99.1,27.3,99.1,28.2z M15.7,104.5v-14h14v14H15.7z"/>
+</svg>

+ 23 - 0
src/assets/svg/global/配置管理.svg

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<g>
+	<path d="M73.8,73h10.8l0.2-0.7c0.9-2.4,1.4-5,1.4-7.6c0-12.2-10.1-22.2-22.5-22.2s-22.5,9.9-22.5,22.2s10.1,22.2,22.5,22.2h1v-9.3
+		h-1c-7.2,0-13.1-5.8-13.1-12.8c0-7.1,5.9-12.8,13.1-12.8c7.2,0,13.1,5.7,13.1,12.8c0,2.4-0.7,4.7-1.9,6.7L73.8,73z"/>
+	<path d="M61.9,107l-5.1,0l-1.5-11l-2.7-1c-3.6-1.3-7-3.2-10-5.6l-2.2-1.8L30,91.9L23,80l8.9-6.8l-0.5-2.8c-0.3-1.9-0.5-3.8-0.5-5.7
+		c0-1.9,0.2-3.8,0.5-5.7l0.5-2.8l-9-6.8l6.9-11.7L40.4,42l2.2-1.8c3-2.5,6.3-4.4,10-5.7l2.7-1l1.5-11h13.8l1.6,11l2.7,1
+		c3.6,1.3,7,3.2,10,5.6L87,42l10.4-4.4l7,11.9l-9,6.8l0.5,2.8c0.7,3.8,0.7,7.6,0,11.3L95.4,73h15.2l-4.9-3.8c0.3-3,0.3-6,0-8.9
+		l5.5-4.2c3.3-2.1,4.3-6.3,2.4-9.7l-8.8-15c-2-3.3-6.2-4.5-9.6-2.8l-6.5,2.6c-2.5-1.8-5.1-3.3-7.9-4.5L79.7,20
+		c-0.2-3.8-3.4-6.9-7.3-6.9H54.9c0,0-0.1,0-0.1,0c-3.9,0-7,3-7.2,6.8l-0.9,6.9c-2.8,1.2-5.5,2.7-7.9,4.5l-6.4-2.6
+		c-3.4-1.7-7.7-0.5-9.6,2.8l-8.8,15c-1.9,3.4-0.9,7.6,2.3,9.6l5.6,4.2c-0.4,3-0.4,6,0,8.9l-5.5,4.2c-3.3,2-4.3,6.4-2.4,9.7l8.8,15
+		c2,3.3,6.2,4.5,9.6,2.8l6.5-2.6c2.4,1.8,5.1,3.3,7.9,4.5l0.9,6.8c0.2,3.9,3.4,6.8,7.2,6.8c0,0,0.1,0,0.1,0h7c1.5,0,2.8-1.2,2.8-2.8
+		v-3.9c0-0.7-0.3-1.4-0.8-2C63.3,107.3,62.6,107,61.9,107z"/>
+	<path d="M113.4,92.7H72.7c-1.5,0-2.7,1.2-2.7,2.7v3.9c0,1.5,1.2,2.6,2.7,2.6h40.7c1.5,0,2.7-1.2,2.7-2.7v-3.9
+		C116.1,93.9,114.9,92.7,113.4,92.7z"/>
+	<path d="M113.4,78.2H72.7c-1.5,0-2.7,1.2-2.7,2.6v3.9c0,1.5,1.2,2.6,2.7,2.6h40.7c0,0,0,0,0,0c1.4,0,2.6-1.2,2.7-2.6v-3.9
+		C116.1,79.4,114.9,78.2,113.4,78.2z"/>
+	<path d="M113.4,107.2H72.7c0,0,0,0,0,0c-1.5,0-2.6,1.2-2.7,2.7v3.9c0,1.5,1.2,2.6,2.7,2.6h40.7h0c1.5,0,2.6-1.2,2.7-2.7v-3.9
+		C116.1,108.4,114.9,107.2,113.4,107.2z"/>
+</g>
+</svg>

+ 22 - 0
src/assets/svg/global/随船人员.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
+<style type="text/css">
+	.st0{stroke:#000000;stroke-miterlimit:10;}
+</style>
+<path class="st0" d="M113.5,112.1c-3.9,8.9-14.3,13-23.1,9.1c-4.1-1.7-7.4-5-9.1-9.1l-1.7-4.1h9.1l-1.4,3c1.6,2.7,4.2,4.7,7.3,5.4
+	v-9.8h-4.4c-1.6,0-3-1.4-3-3c0-1.6,1.4-3,3-3h4.4v-2.3c-2.8-1.6-3.8-5.2-2.1-8c1.6-2.8,5.2-3.8,8-2.1c2.8,1.6,3.8,5.2,2.1,8
+	c-0.5,0.9-1.2,1.6-2.1,2.1v2.3h4.4c1.6,0,3,1.4,3,3c0,1.6-1.4,3-3,3l0,0h-4.4v9.8c3.1-0.7,5.7-2.7,7.3-5.4l-1.4-3h9.1L113.5,112.1z"
+	/>
+<g>
+	<path d="M36.1,47.1h4.5c-0.3,1.5-0.5,3.1-0.5,4.9c0,15.8,12.9,28.7,28.7,28.7c15.8,0,28.7-12.9,28.7-28.7c0-1.8-0.2-3.4-0.5-4.9
+		l4.1,0c1.4-0.1,3-1.7,3-2.9C103.3,24.3,90.6,4.2,68,4.2C45.2,4.2,33.4,24,32,42.4c-0.1,1.1,0.8,2.4,1.5,3.3
+		C34,46.2,35,47.1,36.1,47.1z M70.2,34.1l-4.1,0l-5.3,0.2c-6.9,0.5-11.6,1.9-14.9,4.3h-4.9c2.7-15.8,13.3-26,27-26h0.5
+		C82,12.9,92.3,23.1,95,38.6h-3.3c-3.2-2.4-7.7-3.7-14.2-4.3l-0.8-0.1L70.2,34.1z M52.3,44.6c0.8-0.4,1.6-0.7,2.5-0.9l0.5-0.1
+		c1-0.2,2.2-0.4,3.4-0.6l1.7-0.2l7-0.3h1.5l5,0.1l4.2,0.3l2.8,0.4c0.5,0.1,0.9,0.2,1.3,0.3l0.5,0.1c0.9,0.2,1.9,0.6,2.7,1
+		c2.3,1.1,3.7,2.7,3.7,7.4c0,11.2-9.1,20.2-20.2,20.2c-11.2,0-20.2-9.1-20.2-20.2C48.6,47.2,50,45.7,52.3,44.6z"/>
+	<path d="M76.4,117.1c0-0.6,0-1.2,0-1.7H19.8c1.7-13.7,13.2-23.9,27.1-23.9h36.7c1.8-3,4-5.9,6.4-8.4c-0.3,0-0.6,0-0.9,0H46.8
+		c-9.5,0-18.5,3.7-25.3,10.5s-10.5,15.7-10.5,25.3v5h65.7C76.6,121.6,76.4,119.4,76.4,117.1z"/>
+</g>
+</svg>

+ 53 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,53 @@
+<template>
+  <svg aria-hidden="true" class="svg-icon" :style="`width: ${size}px;height: ${size}px;transform: rotate(${rotate}deg);`">
+    <use :xlink:href="symbolId" :fill="color" />
+  </svg>
+</template>
+
+
+<script lang="ts">
+  import {
+    defineComponent,
+    computed,
+  } from "vue";
+  export default defineComponent({
+    name: "SvgIcon",
+    components: {},
+    props: {
+      prefix: {
+        type: String,
+        default: 'icon'
+      },
+      name: {
+        type: String,
+        required: true
+      },
+      color: {
+        type: String,
+        default: ''
+      },
+      size: {
+        type: [Number, String],
+        default: '18'
+      },
+      rotate: {
+        type: [Number, String],
+        default: 0
+      }
+    },
+    setup(props, { emit }) {
+      const symbolId = computed(() => `#${props.prefix}-${props.name}`)
+      return {
+        symbolId
+      }
+    }
+  })
+</script>
+
+<style scoped>
+.svg-icon {
+  vertical-align: -0.15em;
+  overflow: hidden;
+  fill: currentColor;
+}
+</style>

+ 580 - 0
src/components/cus/CusContent.vue

@@ -0,0 +1,580 @@
+<template>
+  <div class="cus-content" :class="{'cus-content-full': full !== false}">
+    <div class="cc-top-slot" v-if="$slots.top">
+      <slot name="top"/>
+    </div>
+    <div class="cc-field-out" id="field-out" v-if="$slots.fieldOut">
+      <div class="field-out-slot cus-content-form">
+        <slot name="fieldOut"/>
+      </div>
+    </div>
+    <div class="cc-center-slot" v-if="$slots.center">
+      <slot name="center"/>
+    </div>
+    <div class="cc-bottom">
+      <div id="operation" class="operation">
+        <div class="buttons">
+          <slot name="buttons"/>
+        </div>
+        <div class="filters">
+          <el-dropdown :teleported="false">
+            <div class="tools __hover">
+              <SvgIcon name="cus-content_tools" size="18" color="#666666"/>
+            </div>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="showFieldIn = true" v-if="$slots.fieldIn" style="width: 170px;">
+                  <div class="tools-item">
+                    <SvgIcon name="cus-content_field-in" size="18"/>高级筛选
+                  </div>
+                </el-dropdown-item>
+                <el-dropdown-item @click="showFieldColumn = true">
+                  <div class="tools-item">
+                    <SvgIcon name="cus-content_field-column" size="18"/>字段筛选
+                  </div>
+                </el-dropdown-item>
+                <el-dropdown-item>
+                  <div class="tools-item" @click="showContentFixed = true">
+                    <SvgIcon name="cus-content_field-fixed" size="18"/>冻结窗格
+                  </div>
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </div>
+      </div>
+      <div id="table" class="table">
+        <slot name="table"/>
+      </div>
+    </div>
+    <el-dialog
+        v-model="showFieldIn"
+        width="566px"
+        align-center
+        modal-class="field-dialog"
+        :close-on-click-modal="false"
+        :show-close="false"
+    >
+      <template #header>
+        <span>高级筛选设置</span>
+        <SvgIcon class="__hover" name="close_2" size="16" @click="showFieldIn = false"/>
+      </template>
+      <div class="field-in-slot">
+        <slot name="fieldIn"/>
+      </div>
+      <template #footer>
+        <div class="buttons">
+          <div class="cancel __hover" @click="showFieldIn = false">取消</div>
+          <div class="submit __hover" @click="$emit('handleSearch'), showFieldIn = false">确定</div>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog
+        v-model="showFieldColumn"
+        width="790px"
+        align-center
+        modal-class="field-dialog"
+        :close-on-click-modal="false"
+        :show-close="false"
+    >
+      <template #header>
+        <span>字段筛选设置</span>
+        <SvgIcon class="__hover" name="close_2" size="16" @click="showFieldColumn = false"/>
+      </template>
+      <div class="field-column">
+        <div class="fc-block fc-all">
+          <div class="fcb-title">全部字段</div>
+          <div class="fcb-content">
+            <div class="fcbc-filter">
+              <el-input v-model="columnAllInputFilter" placeholder="请输入关键词..." clearable>
+                <template #suffix>
+                  <SvgIcon name="search"/>
+                </template>
+              </el-input>
+            </div>
+            <div class="fcbc-list">
+              <template v-for="(item, index) in tableHeadTempDeep.filter(v => v.allLabel.includes(columnAllInputFilter))" :key="item.allLabel">
+                <div class="table-head-item __hover" @click="item.show = !item.show">
+                  <div class="check" :class="{active: item.show}"/>
+                  <CusEllipsis class="label" :value="item.allLabel"/>
+                </div>
+              </template>
+            </div>
+            <div class="fcbc-bottom">
+              <div>共 {{ tableHeadTempDeep.length }} 条</div>
+              <div class="__hover" @click="handleCheckAllChange">
+                <div class="check" :class="{active: columnIsAllCpt}"/>全选
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="fc-block fc-select">
+          <div class="fcb-title">已选字段</div>
+          <div class="fcb-content">
+            <div class="fcbc-filter">
+              <el-input v-model="columnSelectInputFilter" placeholder="请输入关键词..." clearable>
+                <template #suffix>
+                  <SvgIcon name="search"/>
+                </template>
+              </el-input>
+            </div>
+            <div class="fcbc-list">
+              <template v-for="(item, index) in tableHeadTempDeep.filter(v => v.show && v.allLabel.includes(columnSelectInputFilter))" :key="item.allLabel">
+                <div class="table-head-select-item">
+                  <SvgIcon name="cus-content_field-column-select" size="12" color="#e4e4e4"/>
+                  <CusEllipsis class="label" :value="item.allLabel"/>
+                  <SvgIcon class="__hover" name="close_2" size="12" color="#e4e4e4" @click="item.show = false"/>
+                </div>
+              </template>
+            </div>
+            <div class="fcbc-bottom">
+              <div>已选 {{ tableHeadTempDeep.filter(v => v.show).length }} 条</div>
+              <div v-if="tableHeadTempDeep.filter(v => v.show).length > 0" @click="tableHeadTempDeep.forEach(v => v.show = false)" class="clear __hover">清空</div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <div class="buttons">
+          <div class="cancel __hover" @click="showFieldColumn = false">取消</div>
+          <div class="submit __hover" @click="$emit('update:tableHead', tableHeadTemp), showFieldColumn = false">确定</div>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog
+        v-model="showContentFixed"
+        width="790px"
+        align-center
+        modal-class="field-dialog"
+        :close-on-click-modal="false"
+        :show-close="false"
+    >
+      <template #header>
+        <span>表格冻结</span>
+        <SvgIcon class="__hover" name="close_2" size="16" @click="showContentFixed = false"/>
+      </template>
+      <div class="content-fixed">
+        <div class="cf-item">
+          <el-checkbox v-model="tempContentFixed.full">内容</el-checkbox>
+        </div>
+        <div class="cf-item">
+          <el-checkbox v-model="tempContentFixed.leftFixed">表格左侧</el-checkbox>
+        </div>
+        <div class="cf-item">
+          <el-checkbox v-model="tempContentFixed.rightFixed">表格右侧</el-checkbox>
+        </div>
+      </div>
+      <template #footer>
+        <div class="buttons">
+          <div class="cancel __hover" @click="showContentFixed = false">取消</div>
+          <div class="submit __hover" @click="handleContentFixed">确定</div>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts">
+  import {
+    defineComponent,
+    computed,
+    onMounted,
+    ref,
+    reactive,
+    watch,
+    getCurrentInstance,
+    ComponentInternalInstance,
+    toRefs
+  } from 'vue'
+  import {useStore} from 'vuex'
+  import {useRouter} from 'vue-router'
+
+  export default defineComponent({
+    name: 'CusContent',
+    components: {},
+    props: {
+      tableHead: {
+        required: true,
+        default: () => []
+      },
+      text: {},
+      showText: {
+        default: false
+      },
+      searchTip: {
+        default: '请输入查询关键词'
+      },
+      full: {
+        required: true,
+        default: false
+      }
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const router = useRouter();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+      const state = reactive({
+        textTemp: '',
+        showFieldIn: false,
+        showFieldColumn: false,
+        showContentFixed: false,
+        columnAllInputFilter: '',
+        columnSelectInputFilter: '',
+        tableHeadTemp: [],
+        contentFixed: {
+          full: props.full,
+          leftFixed: true,
+          rightFixed: true
+        },
+        tempContentFixed: {
+          full: props.full,
+          leftFixed: true,
+          rightFixed: true
+        }
+      });
+      watch(() => props.text, (nV: any) => {
+        state.textTemp = nV
+      })
+      watch(() => state.showFieldColumn, (n) => {
+        if (n) {
+          state.tableHeadTemp = JSON.parse(JSON.stringify(props.tableHead))
+        }
+      })
+      watch(() => state.showContentFixed, (n) => {
+        if (n) {
+          state.tempContentFixed = JSON.parse(JSON.stringify(state.contentFixed))
+        }
+      })
+      const tableHeadTempDeep = computed(() => {
+        const list: any = []
+        const deep = (arr: any[], pLabel: string = '') => {
+          arr.forEach(v => {
+            v.allLabel = pLabel ? `${pLabel}-${v.label}` : v.label
+            if (!v.tempFixed && v.fixed) {
+              v.tempFixed = JSON.parse(JSON.stringify(v.fixed))
+            }
+            if (v.children && v.children.length > 0) {
+              deep(v.children, v.allLabel)
+            } else {
+              list.push(v)
+            }
+          })
+        }
+        deep(state.tableHeadTemp)
+        return list
+      })
+
+      const columnIsAllCpt = computed(() => {
+        return tableHeadTempDeep.value.every(v => v.show)
+      })
+
+      const handleCheckAllChange = () => {
+        if (columnIsAllCpt.value) {
+          tableHeadTempDeep.value.forEach(v => {
+            v.show = false
+          })
+        } else {
+          tableHeadTempDeep.value.forEach(v => {
+            v.show = true
+          })
+        }
+      }
+      const handleContentFixed = () => {
+        state.contentFixed = JSON.parse(JSON.stringify(state.tempContentFixed))
+        tableHeadTempDeep.value.forEach((v: any) => {
+          if (v.tempFixed === 'left') {
+            v.fixed = state.contentFixed.leftFixed ? 'left' : false
+          } else if (v.tempFixed === 'right') {
+            v.fixed = state.contentFixed.rightFixed ? 'right' : false
+          }
+        })
+        emit('update:full', state.contentFixed.full)
+        emit('update:tableHead', state.tableHeadTemp)
+        state.showContentFixed = false
+      }
+      onMounted(() => {
+        state.tableHeadTemp = JSON.parse(JSON.stringify(props.tableHead))
+        state.tempContentFixed = JSON.parse(JSON.stringify(state.contentFixed))
+      })
+      return {
+        ...toRefs(state),
+        tableHeadTempDeep,
+        handleCheckAllChange,
+        columnIsAllCpt,
+        handleContentFixed
+      }
+    },
+  })
+</script>
+
+<style scoped lang="scss">
+  .cus-content {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    background-color: #F5FBFF;
+    box-sizing: border-box;
+    .cc-top-slot {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+    }
+    .cc-field-out {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+      height: auto;
+      padding: 14px 24px 0 24px;
+      .field-out-slot {
+        flex: 1;
+        :deep(.el-form-item) {
+          margin-bottom: 12px;
+        }
+      }
+    }
+    .cc-center-slot {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+    }
+    .cc-bottom {
+      background-color: #FFFFFF;
+      flex: 1;
+      z-index: 1;
+      display: flex;
+      flex-direction: column;
+      padding: 12px 16px;
+      .operation {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 12px;
+        .buttons {
+          display: flex;
+          :deep(>*) {
+            margin-right: 10px;
+          }
+        }
+        .filters {
+          height: 36px;
+          :deep(.el-dropdown) {
+            .tools {
+              width: 36px;
+              height: 36px;
+              box-sizing: border-box;
+              border-radius: 4px;
+              border: 1px solid #E4E4E4;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+            }
+            .el-dropdown-menu {
+              padding: 8px 4px;
+            }
+            .el-dropdown-menu__item {
+              padding: 0;
+              .tools-item {
+                padding: 0 8px;
+                display: flex;
+                align-items: center;
+                width: 170px;
+                height: 40px;
+                box-sizing: border-box;
+                .svg-icon {
+                  margin-right: 8px;
+                }
+              }
+            }
+          }
+        }
+      }
+      .table {
+        flex: 1;
+        z-index: 1;
+        display: flex;
+      }
+    }
+    :deep(.field-dialog) {
+      .el-overlay-dialog {
+        overflow: hidden;
+        .el-dialog {
+          border-radius: 8px;
+          .el-dialog__header {
+            padding: 30px 28px 38px 28px;
+            margin: 0;
+            font-size: 18px;
+            font-family: Microsoft YaHei;
+            font-weight: bold;
+            color: #333333;
+            display: flex;
+            justify-content: space-between;
+          }
+          .el-dialog__body {
+            padding: 0 28px;
+          }
+          .el-dialog__footer {
+            padding: 22px 28px 20px 28px;
+            .buttons {
+              width: 100%;
+              display: flex;
+              justify-content: flex-end;
+              >div {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                box-sizing: border-box;
+              }
+              .cancel {
+                margin-right: 10px;
+                width: 72px;
+                height: 36px;
+                background: #F8F8F8;
+                border: 1px solid #E4E4E4;
+                border-radius: 4px;
+                font-size: 14px;
+                font-family: Microsoft YaHei;
+                font-weight: 400;
+                color: #999999;
+              }
+              .submit {
+                width: 72px;
+                height: 36px;
+                background: #0062E9;
+                border-radius: 4px;
+                font-size: 14px;
+                font-family: Microsoft YaHei;
+                font-weight: 400;
+                color: #FFFFFF;
+              }
+            }
+          }
+        }
+      }
+    }
+    .field-column {
+      display: flex;
+      justify-content: space-between;
+      .fc-block {
+        .fcb-title {
+          font-size: 14px;
+          font-family: Microsoft YaHei;
+          font-weight: 400;
+          color: #666666;
+          margin-bottom: 18px;
+        }
+        .fcb-content {
+          height: 488px;
+          border: 1px solid #E4E4E4;
+          border-radius: 4px;
+          display: flex;
+          flex-direction: column;
+          .fcbc-filter {
+            padding: 10px 12px 0 12px;
+          }
+          .check {
+            width: 18px;
+            height: 18px;
+            box-sizing: border-box;
+            border: 1px solid #d3d3d3;
+            border-radius: 2px;
+            margin: 0 10px;
+            cursor: pointer;
+            font-size: 16px;
+            &.active {
+              background-color: #6587ff;
+              border-color: #6587ff;
+              display: grid;
+              place-items: center;
+              &:after {
+                content: '✔';
+                color: #ffffff;
+                font-size: 14px;
+              }
+            }
+            &:hover {
+              opacity: 0.8;
+            }
+          }
+          .fcbc-bottom {
+            margin-top: auto;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            height: 40px;
+            min-height: 40px;
+            background: #FFFFFF;
+            border-top: 1px solid #E4E4E4;
+            border-radius: 0px 0px 4px 4px;
+            padding: 0 10px 0 12px;
+            font-size: 14px;
+            font-family: Microsoft YaHei;
+            font-weight: 400;
+            color: #666666;
+            box-sizing: border-box;
+          }
+        }
+        &.fc-all {
+          width: 480px;
+          .fcbc-list {
+            display: grid;
+            grid-template-columns: repeat(2, 1fr);
+            padding: 20px 12px;
+            row-gap: 22px;
+            column-gap: 30px;
+            overflow-y: auto;
+            .table-head-item {
+              height: 18px;
+              display: flex;
+              align-items: center;
+              line-height: 1;
+              .label {
+                width: 164px;
+              }
+            }
+          }
+          .fcbc-bottom {
+            >div:nth-child(2) {
+              display: flex;
+              align-items: center;
+              .check:after {
+                transform: translateY(-2px);
+              }
+            }
+          }
+        }
+        &.fc-select {
+          width: 244px;
+          .fcbc-list {
+            overflow-y: auto;
+            display: grid;
+            row-gap: 26px;
+            padding: 20px 12px;
+            grid-template-columns: repeat(1, 1fr);
+            .table-head-select-item {
+              display: flex;
+              align-items: center;
+              .svg-icon:first-child {
+                margin-right: 10px;
+              }
+              .label {
+                width: 164px;
+              }
+              .svg-icon:last-child {
+                margin-left: auto;
+              }
+            }
+          }
+          .fcbc-bottom {
+            .clear {
+              font-size: 14px;
+              font-family: Microsoft YaHei;
+              font-weight: 400;
+              color: #0062E9;
+            }
+          }
+        }
+      }
+    }
+    &.cus-content-full {
+      overflow-y: auto;
+      position: absolute;
+    }
+  }
+</style>

+ 115 - 0
src/components/cus/CusContentCard.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="cus-content-card" :class="{'cus-content-card-full': full !== false}">
+    <div class="ccc-top-slot" v-if="$slots.top">
+      <slot name="top"/>
+    </div>
+    <div class="ccc-field-out" id="field-out" v-if="$slots.fieldOut">
+      <div class="field-out-slot">
+        <slot name="fieldOut"/>
+      </div>
+    </div>
+    <div class="ccc-center-slot" v-if="$slots.center">
+      <slot name="center"/>
+    </div>
+
+    <div class="ccc-content">
+      <div class="buttons" v-if="$slots.buttons">
+        <slot name="buttons"/>
+      </div>
+      <div class="table">
+        <slot name="table"/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+  import {
+    defineComponent,
+    computed,
+    onMounted,
+    ref,
+    reactive,
+    watch,
+    getCurrentInstance,
+    ComponentInternalInstance,
+    toRefs
+  } from 'vue'
+  import {useStore} from 'vuex'
+  import {useRouter} from 'vue-router'
+
+  export default defineComponent({
+    name: 'CusContentCard',
+    components: {},
+    props: {
+      full: {
+        default: false
+      }
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const router = useRouter();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+      const state = reactive({
+      });
+      return {
+        ...toRefs(state),
+      }
+    },
+  })
+</script>
+
+<style scoped lang="scss">
+  .cus-content-card {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    background-color: #F5FBFF;
+    box-sizing: border-box;
+    .ccc-top-slot {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+    }
+    .ccc-field-out {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+      height: auto;
+      padding: 14px 24px 0 24px;
+      .field-out-slot {
+        flex: 1;
+        :deep(.el-form-item) {
+          margin-bottom: 12px;
+        }
+      }
+    }
+    .cc-center-slot {
+      margin-bottom: 10px;
+      background-color: #FFFFFF;
+    }
+    .ccc-content {
+      background-color: #FFFFFF;
+      flex: 1;
+      z-index: 1;
+      display: flex;
+      flex-direction: column;
+      padding: 12px 16px;
+      overflow: hidden;
+      .buttons {
+        margin-bottom: 12px;
+      }
+      .table {
+        flex: 1;
+        z-index: 1;
+        display: flex;
+        overflow-y: auto;
+      }
+    }
+    &.cus-content-card-full {
+      overflow-y: auto;
+      .ccc-content {
+        overflow: unset;
+      }
+    }
+  }
+</style>

+ 160 - 0
src/components/cus/CusDialog.vue

@@ -0,0 +1,160 @@
+<template>
+  <el-dialog
+      :style="`
+        --cus-dialog_height: ${height};
+        --cus-dialog_max-height: ${maxHeight};
+        --cus-dialog_min-height: ${minHeight};
+      `"
+      :modal-class="`
+        __cus-dialog
+        ${maxHeight === 'unset' ? '' : '__cus-dialog-auto-height'}
+      `"
+      v-bind="$attrs"
+      v-model="showDialog"
+      :title="title"
+      :width="width"
+      align-center
+      :before-close="beforeClose"
+      :show-close="false"
+  >
+    <template #header>
+      <div class="_cus-dialog-head">
+        <div class="__cdh-title">{{title}}</div>
+        <div class="__cdh-slot">
+          <slot name="head"/>
+        </div>
+        <div class="__cdh-close __hover" @click="CDClose()">
+          <SvgIcon name="close_2" size="14"/>
+        </div>
+      </div>
+    </template>
+    <div class="__cus-dialog-main" :class="{isFull: full !== false}" v-loading="loading">
+      <div class="__cus-dialog-content">
+        <slot/>
+      </div>
+      <div class="__cus-dialog-foot" :style="`justify-content: ${footAlign};padding: ${footPadding};`">
+        <slot name="foot" :close="CDClose" :submit="CDSubmit"/>
+        <template v-if="footAlign === 'center'">
+          <div v-if="showSubmit" class="__cus-dialog-foot_submit __hover" @click="CDSubmit">确定</div>
+          <div v-if="showClose" class="__cus-dialog-foot_cancel __hover" @click="CDClose()">取消</div>
+        </template>
+        <template v-else>
+          <div v-if="showClose" class="__cus-dialog-foot_cancel __hover" @click="CDClose()">取消</div>
+          <div v-if="showSubmit" class="__cus-dialog-foot_submit __hover" @click="CDSubmit">确定</div>
+        </template>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+import {ElMessage, ElMessageBox} from "element-plus";
+
+export default defineComponent({
+  name: 'CusDialog',
+  components: {},
+  props: {
+    show: {required: true, type: Boolean},
+    title: {default: ''},
+    width: {default: '50%'},
+    full: {default: false},
+    submitText: {default: '确定'},
+    showClose: {default: true},
+    showSubmit: {default: true},
+    footAlign: {default: 'center', validator(val: string) {
+      return ['center', 'right'].includes(val)
+    }},
+    footPadding: {default: '16px 26px'},
+    height: {default: '60%'},
+    maxHeight: {default: 'unset'},
+    minHeight: {default: 'unset'},
+    closeConfirm: {default: false},
+    closeConfirmText: {default: {
+      title: null,
+      message: null,
+      submit: null,
+      close: null,
+    }},
+    loading: {
+      default: false,
+      type: Boolean
+    }
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      showDialog: false,
+      closeConfirmTextTemp: {
+        title: '提示',
+        message: '请确认是否关闭?',
+        submit: '确定',
+        close: '取消',
+      }
+    })
+    watch(() => props.show, (n) => {
+      state.showDialog = n
+    })
+    const beforeClose = (done) => {
+      CDClose(done)
+    }
+    const closeConfirmTextCpt: any = computed(() => {
+      return {
+        title: that.$util.isValue(props.closeConfirmText.title) ? props.closeConfirmText.title : state.closeConfirmTextTemp.title,
+        message: that.$util.isValue(props.closeConfirmText.message) ? props.closeConfirmText.message : state.closeConfirmTextTemp.message,
+        submit: that.$util.isValue(props.closeConfirmText.submit) ? props.closeConfirmText.submit : state.closeConfirmTextTemp.submit,
+        close: that.$util.isValue(props.closeConfirmText.close) ? props.closeConfirmText.close : state.closeConfirmTextTemp.close,
+      }
+    })
+    const CDClose = async (done = () => {}) => {
+      if (props.closeConfirm !== false) {
+        await ElMessageBox.confirm(closeConfirmTextCpt.value.message, closeConfirmTextCpt.value.title, {
+          confirmButtonText: closeConfirmTextCpt.value.submit,
+          cancelButtonText: closeConfirmTextCpt.value.close,
+          type: "warning",
+        }).then(() => {
+          emit('update:show', false)
+          emit('close')
+          done()
+        }).catch(() => {})
+      } else {
+        emit('update:show', false)
+        emit('close')
+        done()
+      }
+    }
+    const CDSubmit = () => {
+      emit('submit')
+    }
+    onMounted(() => {
+      state.showDialog = props.show
+    })
+    return {
+      ...toRefs(state),
+      beforeClose,
+      CDClose,
+      CDSubmit,
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 67 - 0
src/components/cus/CusEllipsis.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="cus-ellipsis" ref="ref_main">
+    <div class="cus-ellipsis-temp" ref="ref_temp">{{value}}</div>
+    <el-tooltip
+        :disabled="!isEllipsis"
+        :content="value"
+        placement="top"
+    >
+      <div class="__text-ellipsis">{{value}}</div>
+    </el-tooltip>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+
+export default defineComponent({
+  name: 'CusEllipsis',
+  components: {},
+  props: {
+    value: { required: true, type: String }
+  },
+  setup() {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({})
+    const ref_main = ref()
+    const ref_temp = ref()
+    const isEllipsis = computed(() => {
+      return ref_temp.value?.clientWidth > ref_main.value?.clientWidth
+    })
+    return {
+      ...toRefs(state),
+      isEllipsis,
+      ref_main,
+      ref_temp
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+  .cus-ellipsis {
+    width: 100%;
+    .cus-ellipsis-temp {
+      word-break: break-all;
+      white-space: nowrap;
+      position: absolute;
+      visibility: hidden;
+    }
+  }
+</style>

+ 107 - 0
src/components/cus/CusForm.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="cus-form">
+    <el-form ref="ref_cusForm" :label-width="labelWidth" v-bind="$attrs">
+      <el-row style="display:flex; flex-wrap: wrap">
+        <slot/>
+      </el-row>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick,
+  provide
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+
+export default defineComponent({
+  name: 'CusForm',
+  components: {},
+  props: {
+    labelWidth: {default: '100px'},
+    elementLoadingBackground: {},
+    formView: {default: false}
+  },
+  setup(props, { emit }) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({})
+    const ref_cusForm: any = ref(null)
+    provide('element-loading-background', props.elementLoadingBackground)
+    let _formView: any = ref(props.formView)
+    watch(() => props.formView, (n) => {
+      _formView.value = n
+    })
+    provide('form-view', _formView)
+    const submit = () => {
+      return new Promise((resolve, reject) => {
+        const nodes: any = [];
+        const handleItem = (itemVue: any) => {
+          const _itemValue = itemVue?.__vueParentComponent?.parent
+          if (_itemValue?.type.name === "CusFormColumn") {
+            const errorMessage = _itemValue.setupState.handleValidate()
+            if (errorMessage) {
+              nodes.push({
+                node: _itemValue,
+                message: errorMessage
+              });
+            }
+          }
+          if (itemVue.childNodes && itemVue.childNodes.length > 0) {
+            itemVue.childNodes.forEach((v: any) => {
+              handleItem(v);
+            });
+          }
+        };
+        handleItem(ref_cusForm?.value?.$el);
+        if (nodes.length > 0) {
+          reject(nodes);
+        } else {
+          resolve(null);
+        }
+      });
+    }
+    const handleEnter = () => {
+      emit('handleEnter')
+    }
+    const reset = () => {
+      const handleItem = (itemVue: any) => {
+        const _itemValue = itemVue?.__vueParentComponent?.parent
+        if (_itemValue?.type.name === "CusFormColumn") {
+          _itemValue.setupState.reset()
+        } else {
+          if (itemVue.childNodes && itemVue.childNodes.length > 0) {
+            itemVue.childNodes.forEach((v: any) => {
+              handleItem(v);
+            });
+          }
+        }
+      };
+      handleItem(ref_cusForm?.value?.$el);
+    }
+    return {
+      ...toRefs(state),
+      ref_cusForm,
+      submit,
+      handleEnter,
+      reset
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+</style>

+ 455 - 0
src/components/cus/CusFormColumn.vue

@@ -0,0 +1,455 @@
+<template>
+  <el-col class="cus-form-column" :class="{
+    span1: filterSpan == 1,
+    span2: filterSpan == 2,
+    span3: filterSpan == 3,
+    span4: filterSpan == 4,
+    span5: filterSpan == 5,
+  }" :span="span" :offset="offset" ref="ref_cusFormColumn">
+    <el-form-item :label="label" :label-width="labelWidth" :class="{ required: required !== false }"
+                  :error="errorMessage" :required="required">
+      <slot name="cus" :handleValidate="handleValidate">
+        <template v-if="link === 'input'">
+          <InputCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+              @emitEnter="handleEnter"
+          >
+            <template v-if="$slots.prefix" #prefix>
+              <slot name="prefix"/>
+            </template>
+            <template v-if="$slots.suffix" #suffix>
+              <slot name="suffix"/>
+            </template>
+            <template v-if="$slots.prepend" #prepend>
+              <slot name="prepend"/>
+            </template>
+            <template v-if="$slots.append" #append>
+              <slot name="append"/>
+            </template>
+          </InputCom>
+        </template>
+        <template v-else-if="link === 'select'">
+          <SelectCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+            <slot/>
+          </SelectCom>
+        </template>
+        <template v-else-if="link === 'date'">
+          <DateCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+            <template #default="{cell}">
+              <slot v-bind="cell"/>
+            </template>
+          </DateCom>
+        </template>
+        <template v-else-if="link === 'datetime'">
+          <DateTimeCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+            <template #default="{cell}">
+              <slot v-bind="cell"/>
+            </template>
+          </DateTimeCom>
+        </template>
+        <template v-else-if="link === 'time'">
+          <TimeCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+          </TimeCom>
+        </template>
+        <template v-else-if="link === 'cascader'">
+          <CascaderCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+            <template v-if="$slots.default" #default="{node, data}">
+              <slot v-bind="{node, data}"/>
+            </template>
+          </CascaderCom>
+        </template>
+        <template v-else-if="link === 'switch'">
+          <SwitchCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+          </SwitchCom>
+        </template>
+        <template v-else-if="link === 'radio'">
+          <RadioCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+          </RadioCom>
+        </template>
+        <template v-else-if="link === 'checkbox'">
+          <CheckboxCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+          </CheckboxCom>
+        </template>
+        <template v-else-if="link === 'portOfRegistry'">
+          <PortOfRegistryCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :paramOne="paramOne"
+              :paramTwo="paramTwo"
+              @emitParam="(pOne, pTwo) => {
+                $emit('update:paramOne', pOne), $emit('update:paramTwo', pTwo), handleValidate(pOne, pTwo)
+              }"
+          />
+        </template>
+        <template v-else-if="link === 'residentMooringPoint'">
+          <ResidentMooringPointCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :paramOne="paramOne"
+              :paramTwo="paramTwo"
+              @emitParam="(pOne, pTwo) => {
+                $emit('update:paramOne', pOne), $emit('update:paramTwo', pTwo), handleValidate(pOne, pTwo)
+              }"
+          />
+        </template>
+        <template v-else-if="link === 'dept'">
+          <DeptCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+              @emitDefault="val => $emit('getDefault', val)"
+          />
+        </template>
+        <template v-else-if="link === 'upload'">
+          <UploadCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          />
+        </template>
+        <template v-else-if="link === 'number'">
+          <NumberCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+              @emitEnter="handleEnter"
+          >
+            <template v-if="$slots.prefix" #prefix>
+              <slot name="prefix"/>
+            </template>
+            <template v-if="$slots.suffix" #suffix>
+              <slot name="suffix"/>
+            </template>
+            <template v-if="$slots.prepend" #prepend>
+              <slot name="prepend"/>
+            </template>
+            <template v-if="$slots.append" #append>
+              <slot name="append"/>
+            </template>
+          </NumberCom>
+        </template>
+        <template v-else-if="link === 'input-number'">
+          <InputNumberCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+              @emitEnter="handleEnter"
+          >
+          </InputNumberCom>
+        </template>
+        <template v-else-if="link === 'tree-select'">
+          <TreeSelectCom
+              v-bind="$attrs"
+              :label="labelCpt"
+              :param="param"
+              @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          >
+          </TreeSelectCom>
+        </template>
+      </slot>
+      <div v-if="unit" class="unit">{{unit}}</div>
+    </el-form-item>
+  </el-col>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+import InputCom from './cus-form-link/input.vue'
+import InputNumberCom from './cus-form-link/input-number.vue'
+import SelectCom from './cus-form-link/select.vue'
+import DateCom from './cus-form-link/date.vue'
+import DateTimeCom from './cus-form-link/datetime.vue'
+import TimeCom from './cus-form-link/time.vue'
+import CascaderCom from './cus-form-link/cascader.vue'
+import SwitchCom from './cus-form-link/switch.vue'
+import RadioCom from './cus-form-link/radio.vue'
+import CheckboxCom from './cus-form-link/checkbox.vue'
+import PortOfRegistryCom from './cus-form-link/portOfRegistry.vue'
+import ResidentMooringPointCom from './cus-form-link/residentMooringPoint.vue'
+import DeptCom from './cus-form-link/dept.vue'
+import UploadCom from './cus-form-link/upload.vue'
+import NumberCom from './cus-form-link/number.vue'
+import TreeSelectCom from './cus-form-link/tree-select.vue'
+
+export default defineComponent({
+  name: 'CusFormColumn',
+  components: {
+    InputCom,
+    InputNumberCom,
+    SelectCom,
+    DateCom,
+    DateTimeCom,
+    TimeCom,
+    CascaderCom,
+    SwitchCom,
+    RadioCom,
+    CheckboxCom,
+    PortOfRegistryCom,
+    ResidentMooringPointCom,
+    DeptCom,
+    UploadCom,
+    NumberCom,
+    TreeSelectCom
+  },
+  props: {
+    span: {type: Number, default: 6},
+    filterSpan: { default: null },
+    offset: {type: Number, default: 0},
+    param: {},
+    paramOne: {},
+    paramTwo: {},
+    label: {type: String, default: ''},
+    required: {default: false},
+    labelWidth: {type: String, default: ''},
+    link: {type: String, default: 'input', validator(val: string) {
+      return ['cascader', 'checkbox', 'date', 'datetime', 'input', 'radio', 'select', 'switch', 'portOfRegistry', 'residentMooringPoint', 'dept', 'time', 'upload', 'number', 'input-number', 'tree-select'].includes(val)
+    }},
+    rules: {type: Array, default: () => []},
+    maxLength: {type: Number, default: null},
+    minLength: {type: Number, default: null},
+    defaultErrorMsg: {default: null},
+    unit: {default: '', type: String}
+  },
+  setup(props) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      errorMessage: null
+    })
+    const ref_cusFormColumn: any = ref(null)
+    const isValue = (val: any) => {
+      if (val === null || val === undefined || (typeof val === 'string' && val.trim() === '') || (typeof val === 'object' && val?.length === 0)) {
+        return false
+      }
+      return true
+    }
+    const labelCpt = computed(() => {
+      return props.label.replace(/[::]([^::]*)$/, "")
+    })
+    const rulesCpt = computed(() => {
+      const r = [...props.rules]
+      if (isValue(props.minLength)) {
+        r.unshift({
+          handle: (val: any) => !isValue(val) || (isValue(val) && String(val).length >= props.minLength),
+          message: `内容过短,字数需大于等于${props.minLength}`
+        })
+      }
+      if (isValue(props.maxLength)) {
+        r.unshift({
+          handle: (val: any) => !isValue(val) || (isValue(val) && String(val).length <= props.maxLength),
+          message: `内容过长,字数需小于等于${props.maxLength}`
+        })
+      }
+      const doStr = ['input', 'number', 'input-number'].includes(props.link) ? '输入' : '选择'
+      if (props.required !== false && !props.rules.some((v: any) => v.type === 'default')) {
+        if (['portOfRegistry', 'residentMooringPoint'].includes(props.link)) {
+          r.unshift({
+            handle: (pOne: any, pTwo: any) => isValue(pOne) && isValue(pTwo),
+            message: props.defaultErrorMsg ?? `请${doStr}${labelCpt.value}`
+          })
+        } else {
+          r.unshift({
+            handle: (val: any) => isValue(val),
+            message: props.defaultErrorMsg ?? `请${doStr}${labelCpt.value}`
+          })
+        }
+      }
+      return r
+    })
+    const handleValidate = (val: any = undefined, val2: any = undefined) => {
+      state.errorMessage = null
+      for (let i = 0; i < rulesCpt.value.length; i++) {
+        const item: any = rulesCpt.value[i]
+        if (['portOfRegistry', 'residentMooringPoint'].includes(props.link)) {
+          if (!item.handle(val === undefined ? props.paramOne : val, val2 === undefined ? props.paramTwo : val2)) {
+            state.errorMessage = item.message
+            break
+          }
+        } else {
+          if (!item.handle(val === undefined ? props.param : val)) {
+            state.errorMessage = item.message
+            break
+          }
+        }
+      }
+      return state.errorMessage
+    }
+    const handleEnter = () => {
+      const find = (itemVue: any) => {
+        if (itemVue?.type.name === "CusForm") {
+          itemVue.setupState.handleEnter()
+        } else {
+          find(itemVue.parent)
+        }
+      }
+      find(ref_cusFormColumn?.value?.$el?.__vueParentComponent)
+    }
+    const reset = () => {
+      state.errorMessage = null
+    }
+    watch(() => state.errorMessage, (n) => {
+      const p = ref_cusFormColumn.value?.$el?.getElementsByClassName('el-form-item')?.[0]
+      if (n) {
+        setTimeout(() => {
+          const e = p?.getElementsByClassName('el-form-item__content')?.[0]?.getElementsByClassName('el-form-item__error')?.[0]
+          if (e?.clientHeight) {
+            p.style.marginBottom = `${e.clientHeight + 4}px`
+          }
+        }, 200)
+      } else {
+        p.style.marginBottom = '18px'
+      }
+    })
+    return {
+      ...toRefs(state),
+      handleValidate,
+      handleEnter,
+      ref_cusFormColumn,
+      reset,
+      labelCpt
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.cus-form-column {
+  &.span1 {
+    width: 20%;
+    max-width: 20%;
+    flex: unset;
+  }
+  &.span2 {
+    width: 40%;
+    max-width: 40%;
+    flex: unset;
+  }
+  &.span3 {
+    width: 60%;
+    max-width: 60%;
+    flex: unset;
+  }
+  &.span4 {
+    width: 80%;
+    max-width: 80%;
+    flex: unset;
+  }
+  &.span5 {
+    width: 100%;
+    max-width: 100%;
+    flex: unset;
+  }
+  :deep(.el-form-item) {
+    .el-form-item__label {
+      line-height: 1;
+      text-align: right;
+      display: flex;
+      align-items: center;
+      padding-left: 10px;
+    }
+    .el-form-item__content {
+      flex-wrap: unset;
+      >div:first-child {
+        flex: 1;
+      }
+      .unit {
+        margin-left: 6px;
+        font-size: 14px;
+        font-family: PingFang SC-Regular, PingFang SC;
+        font-weight: 400;
+        color: #606266;
+      }
+    }
+  }
+}
+</style>

+ 92 - 0
src/components/cus/CusPopover.vue

@@ -0,0 +1,92 @@
+<template>
+  <el-popover
+      placement="top-start"
+      trigger="hover"
+      :content="value"
+      popper-class="cus-popover"
+  >
+    <template #reference>
+      <slot/>
+    </template>
+  </el-popover>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  nextTick,
+  ComponentInternalInstance, toRefs
+} from 'vue'
+  import {useStore} from 'vuex'
+  import {useRouter} from 'vue-router'
+
+  export default defineComponent({
+    name: 'CusPopover',
+    props: {
+      value: {
+        required: true
+      },
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const router = useRouter();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    },
+  })
+</script>
+
+<style scoped lang="scss">
+  .cus-table-main {
+    width: 100%;
+    height: 100%;
+    $cus-page-height: 50px;
+    :deep(.cus-table) {
+      width: 100%;
+      //height: calc(100% - #{$cus-page-height} - 20px);
+      .el-checkbox__inner {
+        width: 24px;
+        height: 24px;
+        &:after {
+          width: 6px;
+          height: 14px;
+          left: 8px;
+        }
+      }
+      .el-table__header-wrapper, .el-table__fixed-right, .el-table__fixed {
+        .cell {
+          font-weight: 700;
+          font-size: 16px;
+          color: #333333;
+          height: 26px;
+        }
+        th {
+          background-color: #fafafa;
+        }
+      }
+      .el-table__body-wrapper, .el-table__fixed-body-wrapper {
+        .cell {
+          height: 26px;
+          font-size: 16px;
+          font-family: 微软雅黑;
+          font-weight: 400;
+          color: #797979;
+        }
+      }
+    }
+    .cus-table-page {
+      display: flex;
+      justify-content: flex-end;
+      align-items: flex-end;
+      height: $cus-page-height;
+      margin: 0;
+      padding: 0;
+    }
+  }
+
+</style>

+ 86 - 0
src/components/cus/CusSearchButtons.vue

@@ -0,0 +1,86 @@
+<template>
+  <div class="search-buttons">
+    <div class="search __hover" @click="$emit('handleSearch')">查 询</div>
+    <div class="reset __hover" @click="$emit('handleReset')">重 置</div>
+    <div class="expand __hover" v-if="expandValue !== null" @click="$emit('update:expandValue', !expandValue)">
+      {{expandValue ? '收起' : '展开'}}
+      <SvgIcon name="arrow_2" size="14" :rotate="expandValue ? 270 : 90"/>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  nextTick,
+  ComponentInternalInstance, toRefs
+} from 'vue'
+  import {useStore} from 'vuex'
+  import {useRouter} from 'vue-router'
+
+  export default defineComponent({
+    name: 'CusSearchButtons',
+    props: {
+      expandValue: {
+        default: null
+      }
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const router = useRouter();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+      return {
+      }
+    },
+  })
+</script>
+
+<style scoped lang="scss">
+  .search-buttons {
+    margin-left: auto;
+    display: flex;
+    align-items: flex-start;
+    justify-content: flex-end;
+    margin-bottom: 12px;
+    >div {
+      margin-right: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 14px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      box-sizing: border-box;
+      &:last-child {
+        margin-right: 0;
+      }
+    }
+    .search {
+      width: 74px;
+      height: 32px;
+      background: #0062E9;
+      border-radius: 4px;
+      color: #FFFFFF;
+    }
+    .reset {
+      width: 74px;
+      height: 32px;
+      border: 1px solid #0062E9;
+      border-radius: 4px;
+      color: #3070CF;
+    }
+    .expand {
+      color: #0062E9;
+      margin-top: 8px;
+      .svg-icon {
+        margin-left: 9px;
+      }
+    }
+  }
+</style>

+ 122 - 0
src/components/cus/CusTab.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="cus-tab" :class="`cus-tab-${type}`">
+    <template v-for="item in tabs">
+      <div class="cus-tab-item __hover" :class="{active: item.value === param}" @click="$emit('update:param', item.value)">{{item.name}}</div>
+    </template>
+    <div class="cus-tab-slot">
+      <slot/>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useStore} from 'vuex'
+import {useRouter, useRoute} from 'vue-router'
+
+export default defineComponent({
+  name: 'CusTab',
+  components: {},
+  props: {
+    tabs: {
+      required: true
+    },
+    param: {
+      required: true
+    },
+    type: {default: 'type1', validator(val: string) {
+      return ['type1', 'type2'].includes(val)
+    }},
+  },
+  setup(props, {emit}) {
+    const store = useStore();
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({})
+    return {
+      ...toRefs(state)
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+  .cus-tab {
+    height: 40px;
+    width: 100%;
+    border-bottom: 1px solid #DCDFE5;;
+    display: flex;
+    align-items: center;
+    box-sizing: border-box;
+    .cus-tab-item {
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      position: relative;
+      font-size: 14px;
+      font-family: PingFang SC-Regular, PingFang SC;
+      font-weight: 400;
+      color: #606266;
+      margin-right: 16px;
+      &:last-child {
+        margin-right: 0;
+      }
+      &.active {
+        &:after {
+          content: '';
+          position: absolute;
+          bottom: 0;
+        }
+      }
+    }
+    &.cus-tab-type1 {
+      .cus-tab-item {
+        padding: 0 4px;
+        &.active {
+          color: #0062E9;
+          &:after {
+            width: 100%;
+            height: 2px;
+            bottom: -1px;
+            background-color: #0062E9;
+          }
+        }
+      }
+    }
+    &.cus-tab-type2 {
+      .cus-tab-item {
+        padding: 0 4px;
+        min-width: 77px;
+        margin-right: 6px;
+        &.active {
+          font-weight: 500;
+          color: #0062E9;
+          &:after {
+            width: 100%;
+            height: 5px;
+            bottom: -3px;
+            background-image: url("@/assets/images/cus/cus-tab_type2-active.png");
+            background-repeat: no-repeat;
+            background-size: 100% 100%;
+          }
+        }
+      }
+    }
+    .cus-tab-slot {
+      margin-left: auto;
+    }
+  }
+</style>

+ 401 - 0
src/components/cus/CusTable.vue

@@ -0,0 +1,401 @@
+<template>
+  <div class="cus-table" :class="{'cus-table-normal': !otherStyle, 'cus-table-full': full !== false}">
+    <div class="cus-table-main" ref="ref_tableMain">
+      <el-table
+          v-bind="$attrs"
+          class="ct-table"
+          ref="ref_table"
+          :data="tableData"
+          :border="false"
+          @sort-change="handleSort"
+          @filter-change="handleFilter"
+          @selection-change="v => $emit('update:selected', v)"
+      >
+        <el-table-column v-if="selected" type="selection" :width="selectWidth" align="center" width="52" :selectable="selectable"/>
+        <el-table-column v-if="singled" :width="singledWidth" align="center" width="52" class-name="single-column">
+          <template #default="scope">
+            <div class="single-circle" :class="{active: scope.row[singledKey] === singled[singledKey]}" @click.stop="$emit('update:singled', scope.row)">
+              <div class="single-circle-in"></div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column  v-if="showIndex" type="index" label="序号" align="center" fixed="left" width="60"/>
+        <template v-for="(item, index) in tempTableHead" :key="index">
+          <template v-if="item.children && item.children.length > 0">
+            <el-table-column :label="item.label" v-if="item.show" align="center">
+              <template v-for="(item2, index2) in item.children">
+                <template v-if="item2.children && item.children.length > 0">
+                  <el-table-column :label="item2.label" v-if="item2.show" align="center">
+                    <template v-for="(item3, index3) in item2.children">
+                      <CusTableColumn :item="item3">
+                        <template #[`${item3.value}-column`]="{column}">
+                          <slot :name="`${item3.value}-column`" :column="column">
+                          </slot>
+                        </template>
+                        <template #[`${item3.value}-column-value`]="{scope}">
+                          <slot :name="`${item3.value}-column-value`" :scope="scope">
+                          </slot>
+                        </template>
+                      </CusTableColumn>
+                    </template>
+                  </el-table-column>
+                </template>
+                <template v-else>
+                  <CusTableColumn :item="item2">
+                    <template #[`${item2.value}-column`]="{column}">
+                      <slot :name="`${item2.value}-column`" :column="column">
+                      </slot>
+                    </template>
+                    <template #[`${item2.value}-column-value`]="{scope}">
+                      <slot :name="`${item2.value}-column-value`" :scope="scope">
+                      </slot>
+                    </template>
+                  </CusTableColumn>
+                </template>
+              </template>
+            </el-table-column>
+          </template>
+          <template v-else>
+            <CusTableColumn :item="item">
+              <template #[`${item.value}-column`]="{column}">
+                <slot :name="`${item.value}-column`" :column="column">
+                </slot>
+              </template>
+              <template #[`${item.value}-column-value`]="{scope}">
+                <slot :name="`${item.value}-column-value`" :scope="scope">
+                </slot>
+              </template>
+            </CusTableColumn>
+          </template>
+        </template>
+      </el-table>
+    </div>
+    <div class="ct-page">
+      <div class="total">
+        <template v-if="$util.isValue(selected?.length)">
+          已选中 {{ selected.length }} 条 /
+        </template>
+        共 {{ total }} 条
+      </div>
+      <el-pagination
+          v-if="noPage === false"
+          ref="ref_tablePage"
+          class="__cus-pagination"
+          :currentPage="page"
+          :page-size="pageSize"
+          background
+          :page-sizes="pageSizes"
+          :layout="pageLayoutCpt"
+          :total="Number(total)"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  nextTick,
+  ComponentInternalInstance, toRefs
+} from 'vue'
+  import {useStore} from 'vuex'
+  import {useRouter} from 'vue-router'
+
+  export default defineComponent({
+    name: 'CusTable',
+    props: {
+      tableData: {
+        required: true
+      },
+      tableHead: {
+        default: () => [],
+        required: true
+      },
+      total: {
+        required: true
+      },
+      page: {},
+      pageSize: {},
+      pageSizes: {
+        default: () => [10, 20, 30, 50, 100]
+      },
+      selected: {
+        default: null
+      },
+      selectWidth: {
+        default: 50
+      },
+      singledWidth: {
+        default: 50
+      },
+      singled: {
+        default: null
+      },
+      singledKey: {
+        default: 'id'
+      },
+      noPage: {
+        default: false
+      },
+      showIndex: {
+        default: true
+      },
+      otherStyle: {
+        default: false
+      },
+      noLayout: {
+        default: () => []
+      },
+      full: {
+        default: false
+      },
+      selectable: {}
+    },
+    setup(props, { emit }) {
+      const store = useStore();
+      const router = useRouter();
+      const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+      const ref_table = ref();
+      const ref_tableMain = ref();
+      const ref_tablePage = ref();
+      const state = reactive({
+        tempTableHead: <any>[],
+        pageLayout: ['sizes', 'prev', 'pager', 'next', 'jumper']
+      });
+      watch(() => props.tableHead, (nVal: any) => {
+        formatTableHead(nVal)
+      }, {deep: true})
+      // watch(() => props.tableData, (nVal) => {
+      //   ref_table.value?.doLayout()
+      // }, {deep: true})
+      const pageLayoutCpt = computed(() => {
+        return state.pageLayout.filter((v: string) => props.noLayout.every((s: string) => v !== s)).join(',')
+      })
+
+      const formatTableHead = (tableHead: Array<any>) => {
+        const result = tableHead
+        const deep = (arr: any[]) => {
+          let flag = 0
+          arr.forEach(v => {
+            if (v.children && v.children.length > 0) {
+              v.show = deep(v.children)
+            }
+            if (v.show) {
+              flag++
+            }
+          })
+          return flag > 0
+        }
+        deep(result)
+        state.tempTableHead = result
+      }
+      const handleSizeChange = (val: Number) => {
+        emit('handlePage', {
+          page: 1,
+          pageSize: val
+        })
+      }
+      const handleCurrentChange = (val: Number) => {
+        emit('handlePage', {
+          page: val,
+          pageSize: props.pageSize
+        })
+      }
+      const handleSort = ({ column, prop, order }: any) => {
+        emit('handleSort', {
+          key: prop,
+          value: order
+        })
+      }
+      const handleFilter = (val: any) => {
+        const key = Object.keys(val)[0]
+        let value = val[key].length > 0 ? val[key][0] : null
+        emit('handleFilter', {key, value})
+      }
+      const resetFilter = (key: any) => {
+        key ? ref_table.value.clearFilter([key]) : ref_table.value.clearFilter()
+      }
+      onMounted(() => {
+        formatTableHead(props.tableHead)
+      })
+      return {
+        handleSizeChange,
+        handleCurrentChange,
+        handleSort,
+        resetFilter,
+        handleFilter,
+        ref_table,
+        ref_tableMain,
+        ref_tablePage,
+        ...toRefs(state),
+        pageLayoutCpt
+      }
+    },
+  })
+</script>
+
+<style scoped lang="scss">
+.cus-table {
+  width: 100%;
+  height: 100%;
+  max-height: 100%;
+  display: flex;
+  flex-direction: column;
+  $cus-page-height: 32px;
+  $cus-page-mt: 25px;
+  position: relative;
+  :deep(.el-popper) {
+    max-width: 60% !important;
+  }
+  .cus-table-main {
+    width: 100%;
+    height: calc(100% - #{$cus-page-height} - #{$cus-page-mt}) !important;
+    position: absolute;
+    :deep(.ct-table) {
+      height: 100%;
+    }
+  }
+  :deep(.ct-page) {
+    height: $cus-page-height;
+    font-size: 14px;
+    font-family: Microsoft YaHei;
+    font-weight: 400;
+    color: #999999;
+    display: flex;
+    justify-content: space-between;
+    .total {
+      display: flex;
+      align-items: center;
+    }
+  }
+  &.cus-table-normal {
+    .cus-table-main {
+      :deep(.ct-table) {
+        width: 100%;
+        //height: calc(100% - #{$cus-page-height} - 20px);
+        .el-checkbox__inner {
+          &:after {
+          }
+        }
+        $borderColor: #FFFFFF;
+        $borderWidth: 2px;
+        //  表格左边框
+        &::before, .el-table__border-left-patch {
+          display: none;
+        }
+        //  表格上边框
+        .el-table__inner-wrapper::after {
+          display: none;
+        }
+        //  表格右边框
+        &.el-table--border::after {
+          display: none;
+        }
+        //  表格下边框
+        .el-table__inner-wrapper::before {
+          display: none;
+        }
+        .el-table__header-wrapper, .el-table__fixed-right, .el-table__fixed {
+          .el-table__header {
+            tr {
+              >th {
+                border-left: $borderWidth solid $borderColor;
+                border-top: $borderWidth solid $borderColor;
+                border-right: none;
+                border-bottom: none;
+                background-color: #F5F5F5;
+                height: 52px;
+                .cell {
+                  font-size: 14px;
+                  font-family: 微软雅黑;
+                  font-weight: 400;
+                  color: #666666;
+                }
+              }
+              &:first-child {
+                >th {
+                  border-top: none;
+                  &:first-child {
+                    border-left: none;
+                  }
+                }
+              }
+            }
+          }
+        }
+        .el-table__body-wrapper, .el-table__fixed-body-wrapper {
+          .el-table__body {
+            .el-table__row {
+              >td {
+                border-right: none;
+                border-bottom-color: #F4F4F4;
+                .cell {
+                  font-size: 14px;
+                  font-family: 微软雅黑;
+                  font-weight: 400;
+                  color: #666666;
+                  white-space: nowrap;
+                  overflow: hidden;
+                  text-overflow: ellipsis;
+                  word-break: break-all;
+                  .el-link +.el-link {
+                    margin-left: 10px;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    :deep(.ct-page) {
+      margin-top: auto;
+    }
+  }
+  &.cus-table-full {
+    .cus-table-main {
+      position: unset;
+      height: 100% !important;
+    }
+    :deep(.ct-page) {
+      margin-top: $cus-page-mt;
+    }
+  }
+}
+
+:deep(.single-column) {
+  .cell {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .single-circle {
+      width: 16px;
+      height: 16px;
+      border: 1px solid rgba(96, 98, 102, 0.6);
+      border-radius: 50%;
+      cursor: pointer;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      &.active {
+        background-color: #22a5fe;
+        border-color: #22a5fe;
+      }
+      .single-circle-in {
+        width: 6px;
+        height: 6px;
+        border-radius: 50%;
+        background-color: #FFFFFF;
+      }
+    }
+  }
+}
+</style>

+ 0 - 0
src/components/cus/CusTableCard.vue


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