Browse Source

初始化

hbchen 2 years ago
commit
22469f6ea0
63 changed files with 6515 additions and 0 deletions
  1. 4 0
      .env.development
  2. 4 0
      .env.production
  3. 4 0
      .env.test
  4. 29 0
      .gitignore
  5. 67 0
      README.md
  6. 16 0
      index.html
  7. 11 0
      jsconfig.json
  8. 37 0
      package.json
  9. 0 0
      public/index.css
  10. BIN
      public/logo-site.png
  11. 39 0
      src/App.vue
  12. 10 0
      src/api/common.js
  13. 50 0
      src/api/crm.js
  14. 145 0
      src/api/financialMana.js
  15. 233 0
      src/api/systemMana.js
  16. 35 0
      src/api/user.js
  17. BIN
      src/assets/img/icon/back-circle.png
  18. BIN
      src/assets/img/icon/empty-data.png
  19. BIN
      src/assets/img/icon/institution.png
  20. BIN
      src/assets/img/icon/leftArrow-circle.png
  21. BIN
      src/assets/img/icon/pass-check.png
  22. BIN
      src/assets/img/icon/rightArrow-circle.png
  23. 27 0
      src/assets/img/layout/logo-big.svg
  24. 6 0
      src/assets/img/layout/logo_mini.svg
  25. 4 0
      src/assets/img/layout/user_img.svg
  26. 3 0
      src/assets/svg-icons/financial/tableColumnpPick.svg
  27. 3 0
      src/assets/svg-icons/menu/financialMana.svg
  28. 4 0
      src/assets/svg-icons/menu/recruitmentMana.svg
  29. 1 0
      src/assets/svg-icons/menu/systemMana.svg
  30. 30 0
      src/components/SvgIcon/index.vue
  31. 72 0
      src/components/mPage.vue
  32. 2 0
      src/directives/index.js
  33. 18 0
      src/directives/modules/buttonPermisson.js
  34. 18 0
      src/directives/modules/select-scroll.js
  35. 52 0
      src/layout/headerBar/breadcrumb.vue
  36. 119 0
      src/layout/headerBar/index.vue
  37. 143 0
      src/layout/index.vue
  38. 144 0
      src/layout/sideBar/index.vue
  39. 101 0
      src/layout/sideBar/sidebarItem.vue
  40. 37 0
      src/main.js
  41. 58 0
      src/permission.js
  42. 50 0
      src/router/index.js
  43. 7 0
      src/store/getters.js
  44. 17 0
      src/store/index.js
  45. 116 0
      src/store/modules/router.js
  46. 14 0
      src/store/modules/user.js
  47. 9 0
      src/styles/element.scss
  48. 189 0
      src/styles/main.scss
  49. 2 0
      src/styles/theme.scss
  50. 66 0
      src/utils/request.js
  51. 13 0
      src/utils/validators.js
  52. 7 0
      src/views/404.vue
  53. 121 0
      src/views/ChangePassword.vue
  54. 244 0
      src/views/Login.vue
  55. 671 0
      src/views/financialManagement/components/serviceVarietyDia.vue
  56. 1060 0
      src/views/financialManagement/contractProgress.vue
  57. 414 0
      src/views/financialManagement/financialList.vue
  58. 238 0
      src/views/systemManagement/departmentM.vue
  59. 430 0
      src/views/systemManagement/menuM.vue
  60. 207 0
      src/views/systemManagement/roleM.vue
  61. 435 0
      src/views/systemManagement/rolePermission.vue
  62. 545 0
      src/views/systemManagement/userM.vue
  63. 134 0
      vite.config.js

+ 4 - 0
.env.development

@@ -0,0 +1,4 @@
+# 接口地址
+VITE_APP_API_URL="http://8.136.199.33:8619/api"
+# crm系统地址
+VITE_CRM_SYSTEM_URL="https://rddptest.hzinsights.com/login"

+ 4 - 0
.env.production

@@ -0,0 +1,4 @@
+# 接口地址
+VITE_APP_API_URL="https://hr.hzinsights.com/api"
+# crm系统地址
+VITE_CRM_SYSTEM_URL="https://admin.hzinsights.com/login"

+ 4 - 0
.env.test

@@ -0,0 +1,4 @@
+# 接口地址
+VITE_APP_API_URL="http://8.136.199.33:8619/api"
+# crm系统地址
+VITE_CRM_SYSTEM_URL="https://rddptest.hzinsights.com/login"

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+package-lock.json
+/src/*.d.ts
+
+node_modules
+dist
+hrms_web
+hrms_web.zip
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+

+ 67 - 0
README.md

@@ -0,0 +1,67 @@
+# 弘则研究财务报表后台管理系统
+## 运行
+  1. npm install    --  安装依赖  --
+  2. npm run dev    --  本地调试  --
+  3. npm run build.test    --  测试环境打包  --
+  4. npm run build    --  生产环境打包  --
+  5. node --version = 16.0.0
+
+## 开发文件目录
+  1. /src/api 服务端api接口配置
+  2. /src/assets 静态资源文件夹
+    + svg-icons文件夹 - 自定义svg图标组件所加载的文件夹
+  3. /src/components -- 全局组件文件夹
+    + mPage.vue -- 分页器
+  4. /src/directives 自定义指令文件夹
+    + buttonPermission.js -- 按钮权限控制指令
+  5. /src/layout 页面外层框文件夹
+  6. /src/router 路由文件夹
+  7. /src/store vuex状态管理
+    + modules -- 模块文件夹
+    + index.js -- 批量导入modules中的文件,创建状态管理器
+    + namespaced:true -- 各模块的命名名称为文件名 例如:state.user.token 、dispatch('router/setRoutes')
+  8. /src/styles 样式文件夹
+    + 修改element-plus主题样式文件:element.scss
+  9. /src/utils 工具函数文件夹
+    + request.js -- axios请求函数封装工具
+    + validators.js -- 复用的表单验证项工具
+  10. /src/views 页面文件夹
+  11. permission.js 全局路由守卫
+
+## 开发说明
+  1. 项目对element-plus做了按需导入和自动导入,也对vue中的方法做了自动导入,在.vue文件中使用element-plus和vue的组件和方法是不需要引入
+    但是对于element-plus的命令式组件 例如ElMessage和ELMessageBox还需自己引入。
+    引入的组件和方法在 auto-imports.d.ts 和 components.d.ts 可查看。
+    由于项目需要引入elemet-plus的所有图标库,所以对于element-plus的图标并没有做自动和按需导入
+  2. 项目路由分为固定路由和动态路由,固定路由配置在 **/src/router/index.js** 中,动态路由从服务端获取,存入到vuex中,并通过router.addRoute()方法
+    注入到router中,详情请看store的router模块的 **setRoutes** 方法。
+    需要加入权限管理的按钮也需要存入vuex中,详情请看store的router模块的 **getPermissionButtons** 方法。
+  3. 在开发完一个页面或者模块时,需要到菜单管理模块添加路由,目前项目的菜单等级包括按钮等级只到三级,所以三级菜单下的按钮请添加到对应的二级菜单下
+    1. 添加/编辑菜单
+      + 上级菜单 -- 返回前两级的菜单,不选择则为一级菜单
+      + 路由名称 -- route中的name属性
+      + 路由地址 -- 这一级别的路由,不是完成的路由、
+      + 组件路径 -- 文件路径,从views文件夹开始的路由,不带上文件名 例如 views/systemManagement/menuM
+      + 是否隐藏 -- route中的hidden属性,控制是否在左边的导航栏显示
+      + 是否有Layout -- 这个页面是否在外层框架下面
+    2. 做三级路由时请注意,其实并没有真的三级路由,只是面包屑想要显示三级,真的还不如做个全屏的弹窗,还不用页面间传递参数。
+      三级路由是在二级路由的页面中放入一个router-view,然后跳转到三级页面时将这个二级页面的对应内容去掉。具体请参照
+      **views/systemManagement/roleM.vue** 角色管理页面,如果有更好的方法可以在该文档中写上。 
+  4. 一些按钮或者区域需要加入到权限控制时,需要permission自定义指令,指令接受的参数值为添加按钮时的按钮ID
+    1. 添加/编辑按钮
+      + 上级菜单 -- 该按钮绑定在哪级菜单下,注意三级菜单的按钮需要绑定到二级菜单下
+      + 菜单标题 -- 设置角色权限时的区分,暂时并不会真的修改到按钮的文本
+      + 按钮ID -- 按钮权限控制的唯一标识,唯一不能重复。
+      + 为了避免按钮Id重复,使用菜单各级别的名称:按钮作用标识,例如 添加部门 -- system:dept:add
+  5. 本项目封装了svg图标组件 => src/components/SvgIcon/index.vue
+    1. 组件会加载 src/assets/svg-icons 文件夹下面的svg图片,需要图标方式使用的svg图片的请放到该文件。
+      + 使用方式:svgIcon的 name 属性为 svgIcon-[dir]-[name]  svgIcon:前缀 dir:文件夹名 name:文件名 表示使用 'dir'文件夹下的'name'文件名的svg图片
+    2. 使用的svg图片如果不需要改变颜色,就可以直接使用;需要通过属性改变颜色需要对svg文件做一些修改,如 - 菜单图标
+      + 改变颜色首先需要将svg文件中<svg></svg>标签的fill属性去除,也可以将fill属性设为'currentColor',但是这样只能使用css方式修改颜色。
+      + 使用v-bind修改颜色:需要将需要修改颜色的标签的fill属性去除 方可通过v-bind方式修改颜色 svgIcon上的color属性
+      + 使用css修改颜色:需要将需要修改颜色的标签的fill属性的值设为'currentColor' ,方可通过css修改颜色。
+      + 两种方式可以混用。
+    3. **import ids from 'virtual:svg-icons-names'** 可以获取所有图标的symbolId,即为svgIcon的name属性,返回值为一个数组。
+
+
+

+ 16 - 0
index.html

@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!-- 火狐浏览器兼容 text-align: justify; 文字等分,两端对齐,改为en,失效 -->
+<html lang="zh">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/png" href="/logo-site.png" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1, minimum-scale=1" />
+    <title>弘则财务报表管理系统</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+    <!-- oss SDK -->
+    <!-- <script type="text/javascript" src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.16.0.min.js"></script> -->
+  </body>
+</html>

+ 11 - 0
jsconfig.json

@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "target": "es6",
+    "paths": {
+      "@/*": ["src/*"],
+    },
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules"]
+}

+ 37 - 0
package.json

@@ -0,0 +1,37 @@
+{
+  "name": "undefined",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite --port 8089",
+    "build": "vite build --mode production",
+    "build.test": "vite build --mode test",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.0.9",
+    "axios": "^0.26.0",
+    "element-plus": "^2.2.14",
+    "html2canvas": "1.4.0",
+    "js-base64": "^3.7.2",
+    "js-md5": "^0.7.3",
+    "lodash": "^4.17.21",
+    "nprogress": "^0.2.0",
+    "vue": "^3.2.37",
+    "vue-router": "^4.1.3",
+    "vuex": "^4.0.2"
+  },
+  "devDependencies": {
+    "@iconify-json/ep": "^1.1.7",
+    "@vitejs/plugin-vue": "^3.0.3",
+    "sass": "^1.56.1",
+    "terser": "^5.15.0",
+    "unplugin-auto-import": "^0.11.2",
+    "unplugin-element-plus": "^0.4.1",
+    "unplugin-icons": "^0.14.8",
+    "unplugin-vue-components": "^0.22.4",
+    "vite": "^3.0.7",
+    "vite-plugin-svg-icons": "^2.0.1"
+  }
+}

+ 0 - 0
public/index.css


BIN
public/logo-site.png


+ 39 - 0
src/App.vue

@@ -0,0 +1,39 @@
+<script setup>
+import zhCn from 'element-plus/lib/locale/lang/zh-cn'
+
+const isRouterAlive = ref(true)
+
+const reload=()=>{
+  isRouterAlive.value=false
+  nextTick(()=>{
+    isRouterAlive.value=true
+  })
+}
+
+provide('reload',reload)
+</script>
+
+<template>
+  <el-config-provider :locale="zhCn">
+    <router-view v-if="isRouterAlive"/>
+  </el-config-provider>
+</template>
+
+<style lang="scss">
+
+  .el-table__header{
+    col[name="gutter"]{
+      display: table-cell!important;
+    }
+  }
+  .el-table{
+    th.gutter{
+      display: table-cell!important;
+    }
+  }
+  /* 解决safari浏览器的table表格控件表头与内容列不对齐问题 */
+  .el-table__header,.el-table__body,.el-table__footer{
+    width: 100%;
+    table-layout: fixed!important;;
+  }
+</style>

+ 10 - 0
src/api/common.js

@@ -0,0 +1,10 @@
+import request from "../utils/request"
+
+ // 获取 阿里云 oss获取临时票据
+export function getOSSToken() {
+  return request({
+      url:'/resource/video/oss_sts_token',
+      method:'get'
+  })
+}
+

+ 50 - 0
src/api/crm.js

@@ -0,0 +1,50 @@
+import request from "../utils/request"
+
+// -----------------------------CRM接口
+
+// 获取合同种类列表
+ /**
+  * @param {
+  * product_id - 产品: 1-FICC(默认); 2-权益 - 非必填
+  * } data 
+  * @returns 
+  */
+export function getPermissionList(data) {
+  return request({
+      url:'/crm/contract/permission_list',
+      method:'get',
+      params:data
+  })
+}
+// 获取销售列表
+ /**
+  * @param {
+  * seller_type - 销售类型: 1-FICC(默认); 2-权益 - 非必填
+  * } data 
+  * @returns 
+  */
+export function getSellerList(data) {
+  return request({
+      url:'/crm/company_seller/list',
+      method:'get',
+      params:data
+  })
+}
+// 根据合同编号搜索合同列表
+ /**
+  * 
+  * @param {
+  * page_size - 每页数据量 - 必填
+  * current - 页码 - 必填
+  * keyword - 关键词 - 非必填
+  * product_id - 产品: 0-全部(默认); 1-FICC; 2-权益 - 非必填
+  * } data 
+  * @returns 
+  */
+export function getContractSearchList(data) {
+  return request({
+      url:'/crm/contract/search_list',
+      method:'get',
+      params:data
+  })
+}

+ 145 - 0
src/api/financialMana.js

@@ -0,0 +1,145 @@
+import request from "../utils/request"
+
+// -----------------------------财务列表
+ // 获取套餐列表
+ /**
+  * 
+  * @param {
+  * product_id - 套餐类型: 1-FICC(默认); 2-权益 - 非必填
+  * } data 
+  * @returns 
+  */
+export function getServiceList(data) {
+  return request({
+      url:'/contract/service/list',
+      method:'get',
+      params:data
+  })
+}
+// 获取财务列表
+ /**
+  * 
+  * @param {
+  * page_size - 每页数据量 - 必填
+  * current - 页码 - 必填
+  * keyword - 关键词-合同编号/客户姓名/销售姓名 - 非必填
+  * start_date - 合同创建日期开始:格式2022-11-22 - 非必填
+  * end_date - 合同创建日期结束:格式2022-11-22 - 非必填
+  * service_type - 套餐ID - 非必填
+  * contract_type - 合同类型:1-新签; 2-续约 - 非必填
+  * register_status - 登记状态:1-进行中; 2-已完成 - 非必填
+  * } data 
+  * @returns 
+  */
+export function getRegisterList(data) {
+  return request({
+      url:'/contract/register/list',
+      method:'get',
+      params:data
+  })
+}
+// 获取导出财务列表
+export function registerListExport(data) {
+  return request({
+      url:'/contract/register/export',
+      method:'get',
+      params:data,
+      responseType:'blob'
+  })
+}
+// 合规登记 - 新增
+ /**
+  * 
+  * @param {
+  * 太多了,看代码
+  * } data 
+  * @returns 
+  */
+export function registerAdd(data) {
+  return request({
+      url:'/contract/register/add',
+      method:'post',
+      data
+  })
+}
+// 合规登记 - 编辑
+ /**
+  * 
+  * @param {
+  * 太多了,看代码
+  * } data 
+  * @returns 
+  */
+export function registerEdit(data) {
+  return request({
+      url:'/contract/register/edit',
+      method:'post',
+      data
+  })
+}
+// 开票登记
+export function registerInvoice(data) {
+  return request({
+      url:'/contract/register/invoice',
+      method:'post',
+      data
+  })
+}
+// 到款登记
+export function registerPayment(data) {
+  return request({
+      url:'/contract/register/payment',
+      method:'post',
+      data
+  })
+}
+// 获取财务详情
+ /**
+  * 
+  * @param {
+  * contract_register_id - 合同登记ID - 必填
+  * } data 
+  * @returns 
+  */
+export function registerDetail(data) {
+  return request({
+      url:'/contract/register/detail',
+      method:'get',
+      params:data
+  })
+}
+
+// 更改合同状态
+ /**
+  * 
+  * @param {
+  * contract_register_id - 合同登记ID - 必填
+  * contract_status - 合同状态: 1-已审批; 2-单章寄出; 3-已签回 - 必填
+  * } data 
+  * @returns 
+  */
+export function updateRegisterStatus(data) {
+  return request({
+      url:'/contract/register/update_status',
+      method:'post',
+      data
+  })
+}
+
+// 删除合规
+ /**
+  * 
+  * @param {
+  * contract_register_id - 合同登记ID - 必填
+  * } data 
+  * @returns 
+  */
+export function registerDelete(data) {
+  return request({
+      url:'/contract/register/del',
+      method:'post',
+      data
+  })
+}
+
+

+ 233 - 0
src/api/systemMana.js

@@ -0,0 +1,233 @@
+import request from "../utils/request"
+
+// -----------------------------部门
+ // 获取部门列表
+ /**
+  * 
+  * @param {
+  * dept_name - 部门名称
+  * page_size - 每页条数
+  * current - 页数
+  * } data 
+  * @returns 
+  */
+export function getDepartmentList(data) {
+  return request({
+      url:'/system/dept/list',
+      method:'get',
+      params:data
+  })
+}
+  // 新增部门
+  /**
+   * 
+   * @param {
+  * parent_id - 上级部门id
+  * dept_name - 部门名称
+  * sort - 排序
+  * } data 
+  * @returns 
+  */
+export function addDepartment(data) {
+  return request({
+      url:'/system/dept/add',
+      method:'post',
+      data
+  })
+}
+// 编辑部门
+  /**
+   * 
+   * @param {
+   * parent_id - 上级部门id
+   * dept_name - 部门名称
+   * sort - 排序
+   * } data 
+   * @returns 
+   */
+export function editDepartment(data) {
+  return request({
+      url:'/system/dept/edit',
+      method:'put',
+      data
+  })
+}
+// 删除部门
+  /**
+   * 
+   * @param {
+   * dept_id - 部门id
+   * } data 
+   * @returns 
+   */
+export function deleteDepartment(data) {
+  return request({
+      url:'/system/dept/delete',
+      method:'delete',
+      data
+  })
+}
+
+// ------------------------------菜单
+// 获取列表
+export function getMenuList(data) {
+  return request({
+      url:'/system/menu/all_list',
+      method:'get',
+      params:data
+  })
+}
+
+// 获取当前用户有权限的路由
+export function getAsyncRoutes() {
+  return request({
+      url:'/system/menu/list',
+      method:'get'
+  })
+}
+
+// 获取当前用户有权限的按钮
+export function getPermissionButtons(data) {
+  return request({
+      url:'/system/menu/buttons',
+      method:'get',
+      params:data
+  })
+}
+
+// 添加菜单
+export function addMenu(data) {
+  return request({
+      url:'/system/menu/add',
+      method:'post',
+      data
+  })
+}
+
+// 编辑菜单
+export function editMenu(data) {
+  return request({
+      url:'/system/menu/edit',
+      method:'post',
+      data
+  })
+}
+
+// 删除菜单
+export function deleteMenu(data) {
+  return request({
+      url:'/system/menu/delete',
+      method:'delete',
+      data
+  })
+}
+
+// ------------------------------角色
+// 获取角色列表
+export function getRoleList(data) {
+  return request({
+      url:'/system/role/list',
+      method:'get',
+      params:data
+  })
+}
+
+// 新增角色
+export function addRoleApi(data) {
+  return request({
+      url:'/system/role/add',
+      method:'post',
+      data
+  })
+}
+
+// 获取角色绑定菜单
+export function getRolePermission(data) {
+  return request({
+      url:'/system/role/menu/list',
+      method:'get',
+      params:data
+  })
+}
+
+// 修改角色绑定菜单
+export function setRolePermission(data) {
+  return request({
+      url:'/system/role/menu/edit',
+      method:'put',
+      data
+  })
+}
+
+// 删除角色
+export function deleteRoleApi(data) {
+  return request({
+      url:'/system/role/delete',
+      method:'delete',
+      data
+  })
+}
+
+// ------------------------------用户
+// 获取用户列表
+export function getuserList(data) {
+  return request({
+      url:'/system/admin/list',
+      method:'get',
+      params:data
+  })
+}
+
+// 新增用户
+export function addUserApi(data) {
+  return request({
+      url:'/system/admin/add',
+      method:'post',
+      data
+  })
+}
+
+// 编辑用户
+export function editUserApi(data) {
+  return request({
+      url:'/system/admin/edit',
+      method:'post',
+      data
+  })
+}
+
+// 改变用户状态
+export function editUserStatus(data) {
+  return request({
+      url:'/system/admin/modify/enabled',
+      method:'post',
+      data
+  })
+}
+
+// 重置用户密码
+export function resetUserPsd(data) {
+  return request({
+      url:'/system/admin/modify/pwd',
+      method:'post',
+      data
+  })
+}
+
+// 删除用户
+export function deleteUserApi(data) {
+  return request({
+      url:'/system/admin/del',
+      method:'post',
+      data
+  })
+}
+
+// 初次登录修改密码
+export function changePsdFirst(data) {
+  return request({
+      url:'/system/admin/modify/my/init_pwd',
+      method:'post',
+      data
+  })
+}

+ 35 - 0
src/api/user.js

@@ -0,0 +1,35 @@
+import request from "../utils/request"
+// 登录
+/**
+ * 
+ * @param {
+ * admin_name - 用户名
+ * password - 密码
+ * } data 
+ * @returns 
+ */
+export function authLogin(data) {
+    return request({
+        url:'/auth/login',
+        method:'POST',
+        data
+    })
+}
+// 修改自己的密码
+/**
+ * 
+ * @param {
+ * old_pwd - 旧密码
+ * new_pwd - 新密码
+ * confirm_pwd - 确认密码
+ * } data 
+ * @returns 
+ */
+export function changeSelfPsd(data) {
+    return request({
+        url:'/system/admin/modify/my/pwd',
+        method:'POST',
+        data
+    })
+}
+

BIN
src/assets/img/icon/back-circle.png


BIN
src/assets/img/icon/empty-data.png


BIN
src/assets/img/icon/institution.png


BIN
src/assets/img/icon/leftArrow-circle.png


BIN
src/assets/img/icon/pass-check.png


BIN
src/assets/img/icon/rightArrow-circle.png


+ 27 - 0
src/assets/img/layout/logo-big.svg

@@ -0,0 +1,27 @@
+<svg width="120" height="34" viewBox="0 0 120 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M25.5547 17.4061C25.5547 17.4061 25.4934 17.3594 25.3796 17.2894C25.5955 19.5096 25.5197 22.646 23.8713 24.7436C23.8713 24.7436 19.9327 30.7595 12.7848 29.4408C12.7848 29.4408 9.13792 28.8719 8.39104 25.4438L2.70483 25.4234C2.70483 25.4234 5.53773 31.2934 13.6367 33.0089C13.6367 33.0089 18.6898 34.1963 23.0777 32.0782C23.562 31.3372 29.1666 22.6168 25.5547 17.4061Z" fill="#5F7FB2"/>
+<path d="M32.3496 10.4391C20.4899 7.27656 14.4886 21.7124 14.4886 21.7124C17.5199 22.8093 20.3236 18.5935 20.3236 18.5935C22.5234 15.8365 24.7524 16.9014 25.3768 17.2894C25.4905 17.3594 25.5518 17.4061 25.5518 17.4061C29.1637 22.6168 23.5592 31.3372 23.0632 32.087L23.0253 32.1424C38.6164 24.8166 32.3496 10.4391 32.3496 10.4391Z" fill="#0C3872"/>
+<path d="M19.9706 0.332904C19.9706 0.332904 14.9145 -0.857435 10.5266 1.26359C10.0306 2.01047 4.42611 10.7309 8.0409 15.9415C8.0409 15.9415 8.10217 15.9882 8.21303 16.0582C7.99714 13.838 8.07299 10.7017 9.72138 8.60694C9.72138 8.60694 13.6629 2.58813 20.8225 3.90684C20.8225 3.90684 24.4664 4.49034 25.1987 7.90382L30.8878 7.92424C30.8878 7.92424 28.0666 2.05715 19.9706 0.332904Z" fill="#AB9523"/>
+<path d="M10.5266 1.26367L10.5645 1.20532C-5.02366 8.53117 1.24605 22.9086 1.24605 22.9086C13.1057 26.0712 19.107 11.6354 19.107 11.6354C16.0786 10.5384 13.272 14.7542 13.272 14.7542C11.0751 17.5112 8.84614 16.4463 8.21887 16.0583C8.10801 15.9883 8.04674 15.9416 8.04674 15.9416C4.42613 10.731 10.0306 2.01055 10.5266 1.26367Z" fill="#C54322"/>
+<path d="M42.7387 26.3045C42.4762 26.6137 41.8255 26.7917 40.7811 26.8384C40.7521 26.417 40.6044 26.0124 40.3551 25.6714C41.2566 25.6947 41.7059 25.6218 41.7059 25.4555C41.7789 25.3388 41.8022 24.8165 41.7789 23.8917H40.1772L40.2472 21.1171H42.0269V20.2273H40.0692V19.2558H43.1647V22.0653H41.3938L41.3558 22.9202H43.0276C43.0042 24.27 42.9809 25.1355 42.9575 25.5167C42.9566 25.7942 42.881 26.0663 42.7387 26.3045ZM44.6934 25.1375H44.8014C45.2744 25.0886 45.7496 25.0643 46.2251 25.0645C46.1989 24.9478 46.1522 24.7728 46.0822 24.5685C46.0677 24.4824 46.0432 24.3982 46.0092 24.3176C45.8857 23.8691 45.7317 23.4294 45.5483 23.0018C45.5153 22.9468 45.4917 22.8866 45.4783 22.8239L46.5081 22.4329C47.0717 23.6991 47.5702 24.9933 48.0019 26.3103C47.6693 26.4036 47.2317 26.5466 46.6861 26.7362C46.6609 26.504 46.6009 26.2768 46.5081 26.0623C45.539 26.0872 44.5746 26.2065 43.6286 26.4182C43.6147 26.4188 43.601 26.4222 43.5885 26.4282C43.5759 26.4342 43.5648 26.4428 43.5556 26.4532C43.4127 26.5233 43.331 26.5233 43.3076 26.4532C43.118 25.718 43.0451 25.3271 43.0947 25.2862L43.1647 25.2483C43.2615 25.2214 43.3506 25.172 43.4246 25.104C43.4987 25.0361 43.5556 24.9516 43.5906 24.8574C44.2636 22.9958 44.6358 21.0389 44.6934 19.0603C45.1831 19.0631 45.6714 19.111 46.1522 19.2033L46.1872 19.2733C46.1405 19.4629 46.0472 19.8918 45.8955 20.5541C45.5796 22.1009 45.1783 23.6291 44.6934 25.1316V25.1375Z" fill="#040000"/>
+<path d="M63.5668 26.3046C64.6093 25.6413 65.2015 25.0014 65.3435 24.3849C65.4556 23.7986 65.5035 23.2018 65.4865 22.6052V20.6125H65.8074C66.0671 20.6125 66.3151 20.6125 66.5543 20.6505C66.671 20.6505 66.706 20.7205 66.6593 20.8634C66.5886 21.081 66.5531 21.3086 66.5543 21.5374V22.1442C66.5525 22.9166 66.5047 23.6882 66.4113 24.4549C67.7398 25.3554 68.439 25.865 68.509 25.9836C68.1982 26.2231 67.9056 26.4855 67.6338 26.7684C67.6104 26.7684 67.5287 26.6955 67.3858 26.5526C66.9666 26.1514 66.5274 25.7717 66.07 25.4147C65.5721 26.0299 64.9987 26.5798 64.3633 27.0514C64.1139 26.7858 63.8479 26.5363 63.5668 26.3046ZM64.0628 24.5978V19.2559H68.048V24.5074H66.9803V20.2566H65.1305V24.5978H64.0628ZM68.544 25.0238V19.8656H68.9C69.1261 19.866 69.3521 19.8786 69.5768 19.9036C69.6964 19.9036 69.7315 19.9736 69.6848 20.1165C69.6117 20.3094 69.5751 20.5142 69.5768 20.7205V25.0238H68.544ZM70.1457 25.5577V18.9408H70.5688C70.7833 18.9418 70.9975 18.9545 71.2106 18.9787C71.3273 18.9787 71.3769 19.0254 71.3536 19.1188C71.3469 19.2167 71.3343 19.3141 71.3157 19.4105C71.2961 19.5517 71.2844 19.6939 71.2806 19.8365V26.2404C71.4226 26.7869 70.8391 27.0232 69.5301 26.9493C69.501 26.5542 69.3659 26.1743 69.1392 25.8494C69.9328 25.8922 70.2702 25.795 70.1516 25.5577H70.1457Z" fill="#040000"/>
+<path d="M87.4289 19.3346H91.0554V19.1187H95.4667V20.0815H94.784V22.2492H95.7818V23.247H94.784V27.0164H93.6461V23.247H92.8993V23.387C92.9193 24.0765 92.7934 24.7625 92.53 25.4C92.2665 26.0375 91.8714 26.6122 91.3705 27.0864C91.2042 26.9434 90.9183 26.7305 90.5157 26.4475C90.4492 26.3934 90.3779 26.3455 90.3027 26.3045C90.8093 26.0336 91.2246 25.6191 91.4965 25.113C91.7685 24.607 91.885 24.0319 91.8315 23.46V23.247H90.9562V25.6743H89.8826V25.3826H89.2057V26.1645H88.2108V23.9238L87.8899 24.2798C87.747 24.0435 87.7003 23.9588 87.747 24.0318C87.534 23.6992 87.3677 23.4483 87.251 23.2849C87.9512 22.4298 88.3952 21.3941 88.5318 20.2974H87.4289V19.3346ZM89.2057 22.5701V24.5278H89.8826V22.5701H89.2057ZM89.6696 20.2945C89.6326 20.4221 89.6082 20.553 89.5966 20.6854C89.5225 21.0119 89.425 21.3327 89.3049 21.6453H90.9416V22.2842H91.8169V20.0815H91.105V20.2945H89.6696ZM93.652 22.2842V20.0436H92.9401V22.2842H93.652Z" fill="#040000"/>
+<path d="M114.331 23.2469C114.331 23.1497 114.343 22.9834 114.366 22.7481C114.375 22.5326 114.399 22.3181 114.439 22.1062C115.314 22.1325 115.767 22.1675 115.79 22.2141C115.783 22.2744 115.772 22.3339 115.755 22.3921C115.664 22.6588 115.593 22.9319 115.542 23.209H117.427V25.5926C117.403 25.8318 117.462 25.9369 117.605 25.9135H117.961C118.252 26.0098 118.41 25.7122 118.387 25.0383C118.767 25.1846 119.157 25.3035 119.554 25.3942C119.577 26.532 119.234 27.03 118.524 26.888H117.249C116.514 26.9347 116.194 26.7334 116.289 26.2841V24.1864H115.364C115.17 25.5829 114.114 26.5554 112.199 27.1039C111.991 26.743 111.74 26.4089 111.452 26.109C113.039 25.7998 113.939 25.1715 114.153 24.2243H112.172V23.2644L114.331 23.2469ZM111.735 22.4271C112.603 22.0151 113.396 21.462 114.083 20.7904C114.431 20.9656 114.763 21.1685 115.078 21.3972C115.148 21.4439 115.113 21.5256 114.973 21.6452C114.973 21.6219 114.958 21.6219 114.935 21.6452C114.194 22.199 113.414 22.6982 112.601 23.139L111.735 22.4271ZM114.83 19.7605C114.757 19.4688 114.675 19.177 114.579 18.8853C114.814 18.8461 115.053 18.8343 115.291 18.8503C115.516 18.8211 115.743 18.8211 115.968 18.8503C115.972 18.9223 115.983 18.9938 116.003 19.0632C116.026 19.3725 116.038 19.6088 116.038 19.7751H119.025V21.4819H117.888V20.7146H112.872V21.5315H111.77V19.7547L114.83 19.7605ZM118.039 23.317C117.294 22.6659 116.484 22.0939 115.621 21.6102C115.912 21.395 116.185 21.1569 116.438 20.8984C117.309 21.3331 118.132 21.8571 118.894 22.4621L118.039 23.317Z" fill="#040000"/>
+<path d="M43.4536 9.88193H40.5624V6.86523H38.9519V14.5091H40.5624V11.1627H43.4974V14.5091H45.1078V6.86523H43.4536V9.88193Z" fill="#040000"/>
+<path d="M49.0318 9.04456C47.2696 9.12625 46.3467 10.0628 46.2631 11.8541C46.3486 13.6163 47.2715 14.5246 49.0318 14.5791C50.7939 14.5246 51.7168 13.6163 51.8005 11.8541C51.7188 10.0705 50.7959 9.13403 49.0318 9.04456ZM49.0318 13.59C48.2878 13.59 47.9173 13.0065 47.9173 11.8395C47.9173 10.6725 48.2878 10.0628 49.0318 10.0628C49.8312 10.0628 50.2202 10.655 50.1988 11.8395C50.19 13.0211 49.802 13.6017 49.0318 13.6017V13.59Z" fill="#040000"/>
+<path d="M54.3212 10.1708H54.2804V9.17887H52.8217C52.8217 9.34517 52.8362 9.57857 52.8625 9.88199V14.5091H54.4321V11.6996C54.4714 11.431 54.5408 11.1677 54.6393 10.9148C54.8548 10.711 55.1297 10.5812 55.4241 10.5443H56.1272V9.13803C55.2286 9.0826 54.6247 9.42686 54.3212 10.1708Z" fill="#040000"/>
+<path d="M58.6158 9.17877H57.0054V14.509H58.6158V9.17877Z" fill="#040000"/>
+<path d="M58.6595 6.86523H56.9645V8.22771H58.6595V6.86523Z" fill="#040000"/>
+<path d="M64.1124 10.2553V9.17877H59.7332V10.2553H62.5836L59.6515 13.4354V14.509H64.194V13.4354H61.1773L64.1124 10.2553Z" fill="#040000"/>
+<path d="M67.7884 9.04456C66.0262 9.12625 65.1033 10.0628 65.0197 11.8541C65.1033 13.6163 66.0262 14.5246 67.7884 14.5791C69.5505 14.5246 70.4734 13.6163 70.5571 11.8541C70.4754 10.0705 69.5525 9.13403 67.7884 9.04456ZM67.7884 13.59C67.0444 13.59 66.6739 13.0065 66.6739 11.8395C66.6739 10.6725 67.0444 10.0628 67.7884 10.0628C68.5878 10.0628 68.9768 10.655 68.9554 11.8395C68.9466 13.0211 68.5586 13.6017 67.7884 13.6017V13.59Z" fill="#040000"/>
+<path d="M74.945 9.04456C74.167 9.04456 73.6302 9.30616 73.3346 9.82936L73.2937 9.78852V9.16709H71.7212V14.4974H73.2937V11.3173C73.3754 10.5393 73.6915 10.126 74.2419 10.0774C74.876 10.0774 75.193 10.4907 75.193 11.3173V14.4974H76.8035V11.0401C76.7951 10.7601 76.754 10.482 76.6809 10.2116C76.4796 9.5522 75.899 9.16709 74.945 9.04456Z" fill="#040000"/>
+<path d="M87.2481 9.04456C86.4779 9.04456 85.9382 9.30713 85.6377 9.82936L85.5968 9.78852V9.16709H84.0243V14.4974H85.5968V11.3173C85.6785 10.5393 85.9946 10.126 86.545 10.0774C87.1771 10.0774 87.4942 10.4907 87.4961 11.3173V14.4974H89.1066V11.0401C89.0982 10.7601 89.0571 10.482 88.984 10.2116C88.7915 9.5522 88.2109 9.16709 87.2481 9.04456Z" fill="#040000"/>
+<path d="M92.8265 11.1627C92.1379 10.9964 91.7937 10.8068 91.7937 10.5792C91.7937 10.2495 91.9979 10.0832 92.4122 10.0832C92.8265 10.0832 93.0861 10.2904 93.1153 10.62H94.5741C94.4924 9.57168 93.7766 9.04848 92.4268 9.05042C90.968 9.16129 90.2104 9.68449 90.154 10.62C90.0976 11.5556 90.7453 12.1391 92.0971 12.3705C92.7856 12.5077 93.1416 12.7294 93.1707 13.0299C93.1707 13.4179 92.9228 13.6134 92.4268 13.6134C91.9308 13.6134 91.6128 13.3771 91.5515 12.992H90.0636C90.12 14.0384 90.9048 14.5752 92.418 14.6024C93.9312 14.4935 94.7297 13.9431 94.8133 12.9511C94.8094 12.1167 94.1471 11.5206 92.8265 11.1627Z" fill="#040000"/>
+<path d="M97.3281 9.17877H95.7177V14.509H97.3281V9.17877Z" fill="#040000"/>
+<path d="M97.3718 6.86523H95.6768V8.22771H97.3718V6.86523Z" fill="#040000"/>
+<path d="M102.203 9.8003C101.954 9.30432 101.473 9.05634 100.759 9.05634C99.378 9.13803 98.6341 10.0327 98.5271 11.7404C98.635 13.4209 99.3673 14.302 100.715 14.3866C101.003 14.4112 101.293 14.3592 101.554 14.2357C101.815 14.1122 102.039 13.9216 102.203 13.6835C102.368 14.868 102.011 15.4602 101.13 15.4602C100.578 15.4602 100.275 15.2385 100.219 14.798H98.7342C98.7595 15.965 99.5852 16.5436 101.211 16.5339C103.003 16.5611 103.87 15.7209 103.814 14.0132V9.17887H102.203V9.8003ZM101.17 13.3538C100.482 13.3538 100.138 12.8287 100.138 11.7842C100.138 10.6542 100.482 10.0891 101.17 10.0891C101.859 10.0891 102.203 10.6542 102.203 11.7842C102.203 12.8374 101.859 13.3538 101.17 13.3538Z" fill="#040000"/>
+<path d="M108.359 9.04456C107.581 9.04456 107.044 9.30616 106.749 9.82937H106.708V6.85352H105.138V14.4974H106.708V11.3173C106.789 10.5393 107.106 10.126 107.656 10.0774C108.288 10.0774 108.605 10.4907 108.607 11.3173V14.4974H110.218V11.0401C110.209 10.7601 110.168 10.482 110.095 10.2116C109.902 9.5522 109.322 9.16709 108.359 9.04456Z" fill="#040000"/>
+<path d="M113.564 7.5683L111.953 8.1518V9.18459H110.88V10.2611H111.953V12.8636C111.899 14.0753 112.395 14.653 113.441 14.5966L114.722 14.5149V13.5638C114.545 13.5939 114.365 13.6085 114.185 13.6075C113.715 13.6892 113.507 13.3975 113.564 12.7323V10.2553H114.731V9.17876H113.564V7.5683Z" fill="#040000"/>
+<path d="M118.028 11.1627C117.339 10.9964 116.995 10.8068 116.995 10.5792C116.995 10.2495 117.202 10.0832 117.613 10.0832C118.025 10.0832 118.287 10.2904 118.317 10.62H119.761C119.679 9.57168 118.963 9.04848 117.613 9.05042C116.155 9.16129 115.397 9.68449 115.341 10.62C115.284 11.5556 115.932 12.1391 117.284 12.3705C117.972 12.5077 118.328 12.7294 118.357 13.0299C118.357 13.4179 118.109 13.6134 117.613 13.6134C117.117 13.6134 116.802 13.3771 116.738 12.992H115.25C115.307 14.0384 116.091 14.5752 117.605 14.6024C119.118 14.4935 119.916 13.9431 120 12.9511C120.006 12.1167 119.348 11.5206 118.028 11.1627Z" fill="#040000"/>
+<path d="M80.1644 7.59166H81.0513V13.1466H80.1644V14.5966H83.5896V13.1466H82.7026V7.59166H83.5896V6.14166H80.1644V7.59166Z" fill="#040000"/>
+</svg>

+ 6 - 0
src/assets/img/layout/logo_mini.svg

@@ -0,0 +1,6 @@
+<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M25.5548 17.4063C25.5548 17.4063 25.4935 17.3596 25.3797 17.2896C25.5956 19.5098 25.5197 22.6461 23.8714 24.7438C23.8714 24.7438 19.9327 30.7597 12.7849 29.4409C12.7849 29.4409 9.13798 28.872 8.3911 25.444L2.7049 25.4235C2.7049 25.4235 5.53779 31.2936 13.6368 33.009C13.6368 33.009 18.6899 34.1965 23.0778 32.0784C23.5621 31.3373 29.1666 22.6169 25.5548 17.4063Z" fill="#5F7FB2"/>
+<path d="M32.3495 10.4393C20.4899 7.27668 14.4886 21.7125 14.4886 21.7125C17.5199 22.8095 20.3236 18.5937 20.3236 18.5937C22.5234 15.8366 24.7524 16.9015 25.3767 17.2895C25.4905 17.3596 25.5517 17.4062 25.5517 17.4062C29.1636 22.6169 23.5591 31.3373 23.0631 32.0871L23.0252 32.1425C38.6163 24.8167 32.3495 10.4393 32.3495 10.4393Z" fill="#0C3872"/>
+<path d="M19.9706 0.332904C19.9706 0.332904 14.9145 -0.857435 10.5266 1.26359C10.0306 2.01047 4.42611 10.7309 8.0409 15.9415C8.0409 15.9415 8.10217 15.9882 8.21303 16.0582C7.99714 13.838 8.07299 10.7017 9.72138 8.60694C9.72138 8.60694 13.6629 2.58813 20.8225 3.90684C20.8225 3.90684 24.4664 4.49034 25.1987 7.90382L30.8878 7.92424C30.8878 7.92424 28.0666 2.05715 19.9706 0.332904Z" fill="#AB9523"/>
+<path d="M10.5266 1.26343L10.5645 1.20508C-5.02366 8.53092 1.24605 22.9084 1.24605 22.9084C13.1057 26.0709 19.107 11.6351 19.107 11.6351C16.0786 10.5382 13.272 14.754 13.272 14.754C11.0751 17.511 8.84614 16.4461 8.21887 16.0581C8.10801 15.9881 8.04674 15.9414 8.04674 15.9414C4.42613 10.7307 10.0306 2.01031 10.5266 1.26343Z" fill="#C54322"/>
+</svg>

+ 4 - 0
src/assets/img/layout/user_img.svg

@@ -0,0 +1,4 @@
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="20" cy="20" r="20" fill="#AB9523"/>
+<path d="M10.816 20.946V21.114C10.578 24.362 10.326 25.636 9.976 25.972C9.822 26.112 9.682 26.14 9.416 26.14C9.108 26.14 8.338 26.126 7.526 26.042C7.708 26.336 7.834 26.77 7.848 27.078C8.646 27.12 9.43 27.134 9.836 27.106C10.298 27.064 10.564 26.966 10.858 26.672C11.32 26.182 11.586 24.838 11.838 21.422C11.852 21.282 11.866 20.946 11.866 20.946H10.816ZM7.386 18.048C7.218 19.42 6.924 21.212 6.672 22.332L7.722 22.5C7.96 21.366 8.254 19.504 8.45 18.048H7.386ZM14.89 14.702C14.484 18.202 13.602 23.074 12.79 26.028L13.742 26.224C14.582 23.298 15.548 18.496 16.08 14.884L14.89 14.702ZM11.754 25.622L11.908 26.714C13.7 26.504 16.276 26.21 18.698 25.902L18.684 24.922C16.136 25.188 13.462 25.468 11.754 25.622ZM16.486 20.82C17.452 22.822 18.292 25.482 18.53 27.19L19.566 26.91C19.3 25.16 18.446 22.542 17.424 20.54L16.486 20.82ZM7.428 20.946L7.302 21.898H11.236V20.946H7.428ZM7.82 18.048V19.042H11.852V14.828H6.952V15.808H10.83V18.048H7.82ZM24.508 24.404C25.39 25.118 26.496 26.154 27.042 26.784L27.714 26.014C27.154 25.398 26.02 24.432 25.152 23.76L24.508 24.404ZM21.442 14.996V23.494H22.422V15.948H26.454V23.466H27.476V14.996H21.442ZM31.676 14.352V25.636C31.676 25.902 31.564 25.986 31.298 26C31.032 26 30.15 26.014 29.142 25.986C29.31 26.28 29.478 26.756 29.534 27.05C30.822 27.05 31.606 27.022 32.068 26.854C32.516 26.672 32.698 26.364 32.698 25.636V14.352H31.676ZM29.058 15.514V23.9H30.052V15.514H29.058ZM23.92 16.914V20.89C23.92 22.808 23.556 24.908 20.616 26.364C20.826 26.518 21.162 26.924 21.26 27.148C24.41 25.608 24.914 23.06 24.914 20.904V16.914H23.92Z" fill="white"/>
+</svg>

+ 3 - 0
src/assets/svg-icons/financial/tableColumnpPick.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 0H12V1.71429H0V0ZM0 10.2857H7.5V12H0V10.2857ZM0 5.14286H12V6.85714H0V5.14286V5.14286Z" fill="#666666"/>
+</svg>

+ 3 - 0
src/assets/svg-icons/menu/financialMana.svg

@@ -0,0 +1,3 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
+<path d="M10 0C4.48214 0 0 4.48214 0 10C0 15.5179 4.48214 20 10 20C15.5179 20 20 15.5179 20 10C20 4.48214 15.5179 0 10 0ZM10 18.5714C5.26786 18.5714 1.42857 14.7321 1.42857 10C1.42857 5.26786 5.26786 1.42857 10 1.42857C14.7321 1.42857 18.5714 5.26786 18.5714 10C18.5714 14.7321 14.7321 18.5714 10 18.5714ZM10.8036 8.57143H14.2857V10H10.7321V11.4286H14.2857V12.8571H10.7321V17.1786H9.30357V12.8571H5.71429V11.4286H9.30357V10H5.71429V8.57143H9.23214L5.98214 5.32143L7 4.30357L10.0357 7.33929L13.0714 4.30357L14.0893 5.32143L10.8036 8.57143Z" />
+</svg>

+ 4 - 0
src/assets/svg-icons/menu/recruitmentMana.svg

@@ -0,0 +1,4 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
+<path d="M10 20C4.47708 20 0 15.5229 0 10C0 4.47708 4.47708 0 10 0C15.5229 0 20 4.47708 20 10C20 15.5229 15.5229 20 10 20ZM10 18.75C14.8325 18.75 18.75 14.8325 18.75 10C18.75 5.1675 14.8325 1.25 10 1.25C5.1675 1.25 1.25 5.1675 1.25 10C1.25 14.8325 5.1675 18.75 10 18.75Z" />
+<path d="M13.43 7.66988V6.88988H12.16V7.66988H13.43ZM13.43 8.99988V8.19988H12.16V8.99988H13.43ZM10.18 8.19988V8.99988H11.47V8.19988H10.18ZM10.18 6.88988V7.66988H11.47V6.88988H10.18ZM14.11 6.32988V9.54988H9.51997V6.32988H11.47V5.37988H12.16V6.32988H14.11ZM6.76997 12.1499L8.06997 11.8699V10.5599H6.76997V12.1499ZM8.06997 6.48988H6.76997V7.86988H8.06997V6.48988ZM6.76997 8.49988V9.92988H8.06997V8.49988H6.76997ZM9.28997 11.6099L9.33997 12.2599L8.73997 12.3899V14.4899H8.06997V12.5399C7.14997 12.7499 6.25997 12.9499 5.54997 13.1099L5.40997 12.4199C5.61997 12.3799 5.85997 12.3299 6.10997 12.2799V6.48988H5.48997V5.80988H9.31997V6.48988H8.73997V11.7299L9.28997 11.6099ZM14.57 10.8499H11.12C11.05 11.1499 10.96 11.4699 10.88 11.7499H14.06C14.06 11.7499 14.05 11.9599 14.03 12.0599C13.88 13.3699 13.74 13.9399 13.48 14.1799C13.29 14.3599 13.1 14.4099 12.8 14.4299C12.55 14.4499 12.02 14.4399 11.48 14.4199C11.46 14.1999 11.38 13.9299 11.25 13.7399C11.79 13.7899 12.33 13.7999 12.53 13.7999C12.71 13.7999 12.82 13.7899 12.9 13.7099C13.06 13.5799 13.18 13.1999 13.29 12.3699H9.92997C10.09 11.9499 10.25 11.3899 10.39 10.8499H9.05997V10.2199H14.57V10.8499Z"/>
+</svg>

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


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

@@ -0,0 +1,30 @@
+<script setup>
+import { computed } from 'vue'
+const props = defineProps({
+  // prefix: {
+  //   type: String,
+  //   default: 'icon'
+  // },
+  name: {
+    type: String,
+    required: true
+  },
+  color: {
+    type: String,
+    default: '#333'
+  },
+  size: {
+    type: String,
+    default: '1em'
+  }
+})
+
+const symbolId = computed(() => `#${props.name}`)
+</script>
+
+<template>
+  <svg aria-hidden="true" class="svg-icon" :width="props.size" :height="props.size">
+    <use :xlink:href="symbolId" :fill="props.color" />
+  </svg>
+</template>
+

+ 72 - 0
src/components/mPage.vue

@@ -0,0 +1,72 @@
+<script setup>
+  const props=defineProps({
+    page_no:{
+      type:Number,
+      default:1
+    },
+    pageSize: {
+			type:Number,
+			default:10
+		},
+		total: {
+			type:Number,
+			default:0
+		}
+  })
+  const emits = defineEmits(['handleCurrentChange','handleSizeChange'])
+  // 页码改变
+  const handleCurrentChange=(page)=>{
+    emits('handleCurrentChange',page)
+  }
+  // 每页数量变化
+  const handleSizeChange=(pageSize)=>{
+    emits('handleSizeChange',pageSize)
+  }
+</script>
+
+<template>
+  <el-pagination
+    layout='prev, pager, next, jumper, ->, total,sizes'
+    :current-page="page_no"
+    @current-change="handleCurrentChange"
+    @size-change="handleSizeChange"
+    :page-size="pageSize" 
+    :total="total"
+    v-show="total!=0"
+    class="pagination"
+    id="pagination"
+  >
+  </el-pagination>
+</template>
+<style lang="scss" scoped>
+  .pagination{
+    padding: 0 10px;
+    display: flex;
+    align-items: center;
+    height: 32px;
+    background-color: white;
+  }
+</style>
+<style lang="scss">
+  #pagination{
+    *{
+      font-size: 12px;
+    }
+    .el-pagination__rightwrapper{
+      order: -1;
+      justify-content: flex-start;
+    }
+    .el-input__wrapper{
+      height: 28px;
+      box-sizing: border-box;
+    }
+    .el-pagination__editor.el-input{
+      display: flex;
+      align-items: center;
+    }
+    .el-select .el-input{
+      width: 92px;
+    }
+  }
+
+</style>

+ 2 - 0
src/directives/index.js

@@ -0,0 +1,2 @@
+export * from './modules/buttonPermisson'
+export * from './modules/select-scroll'

+ 18 - 0
src/directives/modules/buttonPermisson.js

@@ -0,0 +1,18 @@
+import store from "@/store"
+
+export const permission={
+  mounted(el,binding) {
+    let {value} = binding
+    if(value && typeof(value)=='string'){
+      // 拿出所有按钮的code
+      let buttonCodes = store.getters.permissionButtons.map(item => item.button_code)
+      if(!buttonCodes.includes(value)){
+        // 没有权限,删除dom
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    }else{
+      throw new Error('permission指令只接受字符串类型')
+    }
+
+  },
+}

+ 18 - 0
src/directives/modules/select-scroll.js

@@ -0,0 +1,18 @@
+import {debounce} from 'lodash/function'
+
+export const optionsLoadMore={
+  // 使用该指令,请将绑定的<el-select></el-select>标签的teleported置为false,否则会找不到
+  mounted(el,binding) {
+    if(!(binding.value instanceof Function)){
+      console.error("optionsLoadMore指令的参数必须是回调函数");
+      return 
+    }
+		const SELECT_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap');
+    SELECT_DOM.addEventListener('scroll', debounce(function () {
+      const CONDITION = parseInt(this.scrollHeight - this.scrollTop) <= this.clientHeight+10
+      if (CONDITION) {
+        binding.value()
+      }
+    },200))
+  },
+}

+ 52 - 0
src/layout/headerBar/breadcrumb.vue

@@ -0,0 +1,52 @@
+<script setup>
+  import {useRoute,useRouter,onBeforeRouteUpdate} from 'vue-router'
+
+  const route = useRoute()
+  const router = useRouter()
+  // App.vue provide的reload方法 刷新路由
+  const reload = inject('reload')
+
+  const a=ref('')
+
+  const routeClick = (item)=>{
+    if(route.path == item.path) {
+      reload();
+    }else {
+      router.push({path:item.path})
+    };
+  }
+  // 可以增加路由的个数替换监听 
+  watch(()=>route.path,(newVal)=>{
+    // 合规登记
+    if(newVal == '/financial/list/contractProgress'){
+      if(route.query.type=='invoice'){
+        route.matched[2].meta.title='开票登记'
+      }else if(route.query.type=='compliance'){
+        route.matched[2].meta.title='合规登记'
+      }else if(route.query.type=='placement'){
+        route.matched[2].meta.title='到款登记'
+      }else{
+        route.matched[2].meta.title='查看登记'
+      }
+    }
+  },{immediate:true})
+</script>
+
+<template>
+  <el-breadcrumb class="header-breadcrumb">
+    <el-breadcrumb-item v-for="(item,index) in route.matched" :key="item.path">
+      <span v-if="index!=(route.matched.length-1)" class="canClick" @click.stop="routeClick(item)">
+        {{ item.meta.title }}
+      </span>
+      <span v-else style="font-size:16px;">{{ item.meta.title }}</span>
+    </el-breadcrumb-item>
+  </el-breadcrumb>
+</template>
+  
+<style lang="scss" scoped>
+  .canClick{
+    cursor:pointer;
+    color:$themeColor;
+    font-size: 16px;
+  }
+</style>

+ 119 - 0
src/layout/headerBar/index.vue

@@ -0,0 +1,119 @@
+<script setup>
+  import {useRouter} from "vue-router"
+  import breadcrumb from "./breadcrumb.vue"
+  const router = useRouter()
+
+  const emits=defineEmits(['toggleCollapse'])
+  const props = defineProps({
+    isCollapse:{
+      type:Boolean,
+      default:false
+    }
+  })
+  const realName = JSON.parse(localStorage.getItem('userInfo')).real_name
+  const collapseHandler = ()=>{
+    emits('toggleCollapse')
+  }
+  const resetpwd = ()=>{
+    router.push({path:'/changePassword'})
+  }
+  const logout = ()=>{
+    localStorage.removeItem('fsms_token')
+    localStorage.removeItem('userInfo')
+    location.reload()
+  }
+  const gotoCrmSystem=()=>{
+    window.open(import.meta.env.VITE_CRM_SYSTEM_URL,'_blank')
+  }
+</script>
+
+<template>
+    <div id="headerBar-container" :style="{left:isCollapse?'70px':''}">
+      <div class="headerBar-left">
+        <div class="coll_btn" @click="collapseHandler">
+          <el-icon size="22px" color="#606266">
+            <Expand v-show="!isCollapse" />
+            <Fold v-show="isCollapse" />
+          </el-icon>
+        </div>
+        <breadcrumb />
+      </div>
+      <div class="headerBar-right">
+        <div class="crm-system" @click="gotoCrmSystem">
+          CRM系统
+        </div>
+        <el-dropdown trigger="click" >
+          <span class="el-dropdown-link userinfo-inner">
+            <div class="userinfo-inner-circle"><img src="@/assets/img/layout/user_img.svg" /></div>
+            {{realName}},欢迎您!
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item divided @click.native="resetpwd">修改密码</el-dropdown-item>
+              <el-dropdown-item divided @click.native="logout">退出登录</el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  #headerBar-container{
+    height: 60px;
+    box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+    border-left: solid 1px #eee;
+    border-bottom: solid 1px #eee;
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 30px;
+    position: fixed;
+    left: 200px;
+    right: 0;
+    top: 0;
+    z-index: 1000;
+    background-color: white;
+    transition: left 0.3s ease-in-out;
+    .headerBar-left{
+      display: flex;
+      align-items: center;
+      .coll_btn{
+        cursor: pointer;
+        margin-right: 20px;
+        display: flex;
+        align-items: center;
+      }
+    }
+    .headerBar-right{
+      display: flex;
+      align-items: center;
+      .crm-system{
+        font-size: 16px;
+        color: $themeColor;
+        cursor: pointer;
+      }
+      .userinfo-inner {
+        cursor: pointer;
+        color: #333333 !important;
+        overflow: hidden;
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+        outline: none;
+        .userinfo-inner-circle{
+          // width: 40px;
+          // height: 40px;
+          // border-radius: 20px;
+          // background-color: #D9D9D9;
+          margin: 0 10px 0 60px;
+        }
+        &:hover{
+          color: $themeColor;
+        }
+      }
+    }
+
+  }
+</style>

+ 143 - 0
src/layout/index.vue

@@ -0,0 +1,143 @@
+<script setup>
+  import { ElMessageBox } from "element-plus";
+  import md5 from 'js-md5'
+
+  import headerBar from "./headerBar/index.vue"
+  import sideBar from "./sideBar/index.vue"
+  import {changePsdFirst} from '@/api/systemMana'
+
+  const isCollapse = ref(false)
+  if (document.body.clientWidth <= 1500) {
+    isCollapse.value = true;
+  }
+  const toggleCollapse = ()=>{
+    isCollapse.value = !isCollapse.value
+  }
+  const changePsdFormEl = ref()
+  const changePwd=ref(JSON.parse(localStorage.getItem('userInfo')).change_pwd)
+  const showChangeDia = ref(false)
+  const changePsd=reactive({
+    newPsd:'',
+    confirmPsd:''
+  })
+  const rule={
+    newPsd:{required:true,message:"新密码不能为空",trigger:"blur"},
+    confirmPsd:{required:true,message:"确认新密码不能为空",trigger:"blur"}
+  }
+  //----------method
+    // 提交
+  const submit=()=>{
+    changePsdFormEl.value.validate((valid)=>{
+      if(valid){
+        if(changePsd.newPsd != changePsd.confirmPsd){
+          ElMessage.warning('新密码两次输入不一致,请核对!')
+          return
+        }
+        let newPassword = md5(changePsd.newPsd)
+        let params={
+          new_pwd:newPassword,
+          confirm_pwd:newPassword
+        }
+        // console.log(params);
+        changePsdFirst(params).then(res=>{
+          ElMessage.success('修改密码成功,请重新登录')
+          localStorage.removeItem('fsms_token')
+          localStorage.removeItem('userInfo')
+          showChangeDia.value=false
+          setTimeout(()=>{
+            location.reload()
+          },1200)
+        })
+      }
+    })
+  }
+
+  // changePwd 为true需要修改初始密码
+  if(changePwd.value){
+    ElMessageBox.confirm('当前使用的是初始密码,请您修改密码','修改密码',{
+      cancelButtonText:'退出',
+      confirmButtonText:'立即修改',
+      closeOnPressEscape:false,
+      type: 'warning'
+    }).then(res=>{
+      showChangeDia.value=true
+    }).catch(()=>{
+      localStorage.removeItem('fsms_token')
+      localStorage.removeItem('userInfo')
+      location.reload()
+    })
+  }
+</script>
+
+<template>
+    <div id="app-container">
+      <!-- 侧边导航栏 -->
+      <side-bar :isCollapse="isCollapse"/>
+      <div id="main-container" :style="{marginLeft:isCollapse ?'70px':''}">
+        <header-bar @toggle-collapse="toggleCollapse" :isCollapse="isCollapse"/>
+        <div id="app-main">
+            <router-view v-slot="{ Component }">
+            <!-- 使用transition 每个vue文件必须要有一个根节点 和vue2一样 -->
+              <transition name="fade" mode="out-in">
+                  <component :is="Component" :key="$route.name" />
+              </transition>
+            </router-view>
+        </div>
+      </div>
+      <!-- 修改密码弹窗 -->
+      <el-dialog title="修改密码" 
+        v-model="showChangeDia" 
+        width="500px"
+        :show-close="false"
+        :close-on-press-escape="false"
+        :close-on-click-modal="false">
+          <el-form :model="changePsd" label-position="right"  :rules="rule" ref="changePsdFormEl"
+          style="padding: 0 52px;">
+            <el-form-item prop="newPsd">
+              <el-input type="password" placeholder="请输入新密码(不超过20个字符)" v-model="changePsd.newPsd"
+              maxlength="20" show-password/>
+            </el-form-item>
+            <el-form-item prop="confirmPsd">
+              <el-input type="password" placeholder="请再次输入新密码(不超过20个字符)" v-model="changePsd.confirmPsd"
+              maxlength="20" show-password/>
+            </el-form-item>
+          </el-form>
+          <template #footer>
+            <el-button @click="submit" type="primary" style="color: white;">确定</el-button>
+          </template>
+      </el-dialog>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  #app-container{
+    height: 100%;
+    width: 100%;
+    min-width: 800px;
+    #main-container{
+      position: relative;
+      height: 100vh;
+      margin-left: 200px;
+      padding-top: 60px;
+      box-sizing: border-box;
+      transition: all 0.3s ease-in-out;
+      #app-main{
+        padding: 30px;
+        // min-height: 100%;
+        height: 100%;
+        background-color: #F2F6FA;
+        box-sizing: border-box;
+        overflow: auto;
+        .fade-enter-from,.fade-leave-to{
+            opacity:  0;/*透明度*/
+        }
+        .fade-enter-to,.fade-leave-from{
+            opacity: 1;/*透明度*/
+        }
+        .fade-enter-active,.fade-leave-active{
+            transition: all 0.2s ease;
+        }
+      }
+    }
+  }
+</style>

+ 144 - 0
src/layout/sideBar/index.vue

@@ -0,0 +1,144 @@
+<script setup>
+  import sidebarItem from './sidebarItem.vue';
+  import {useRoute} from 'vue-router'
+  import {useStore} from 'vuex'
+  import {routes} from '@/router/index'
+
+  const route = useRoute()
+  const store = useStore()
+
+  const props = defineProps({
+    isCollapse:{
+      type:Boolean,
+      default:false
+    }
+  })
+  let activePath = ref('')
+
+  watch(()=> route.path,(newVal,oldVal)=>{
+    activePath.value=newVal
+    console.log(newVal,'newVal');
+  },{immediate:true})
+
+  const navlists = store.getters.routes
+  // console.log(route);
+//  -------------------------------method
+
+</script>
+
+<template>
+  <div id="sideBar-container" :style="{width:isCollapse?'70px':''}">
+    <div class="logo_cont" >
+      <img class="logo" v-if="!isCollapse" src="@/assets/img/layout/logo-big.svg"/>
+      <img class="logo_coll" v-else src="@/assets/img/layout/logo_mini.svg"/>
+    </div>
+    <el-scrollbar wrap-class="scrollbar-wrapper" height="100%" max-height="100%" id="sideBar-scrollbar">
+      <!--导航菜单-->
+      <el-menu
+        mode="vertical"
+        background-color="#ffffff" 
+        text-color="#666666"
+        active-text-color="#ffffff"
+        :default-active="activePath"
+        :collapse="isCollapse"
+        unique-opened
+        router
+        class="el-menu-vertical-demo"
+      >
+        <sidebarItem v-for="routeItem in navlists" :key="routeItem.path" 
+          :item="routeItem" :base-path="routeItem.path" :active="activePath" :isCollapse="isCollapse"/>
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+  
+<style lang="scss" scoped>
+  #sideBar-container{
+    height: 100vh;
+    width: 200px;
+    transition: width 0.3s ease-in-out;
+    overflow: hidden;
+    position: fixed;
+    z-index: 99;
+    left: 0;
+    top: 0;
+    background-color:#ffffff !important;
+    border-bottom: 1px solid #eaeaea;
+    .logo_cont {
+      padding: 13px 0;
+      display: flex;
+      align-items: center;
+      margin-left: 20px;
+      .logo {
+        width: 120px;
+        height: 34px;
+        display: block;
+        overflow: hidden;
+        object-fit: contain;
+      }
+      .logo_coll {
+        width: 34px;
+        height: 34px;
+        display: block;
+        overflow: hidden;
+        margin-left: -2px;
+      }
+    }
+    #sideBar-scrollbar{
+      height: calc(100% - 58px);
+      max-height:calc(100% - 58px);
+    }
+  }
+
+</style>
+<style lang="scss">
+  #sideBar-container{
+    .el-menu{
+      border-right: none;
+    }
+    .el-sub-menu{
+      .el-sub-menu__icon-arrow{
+        visibility: hidden;
+      }
+      .custem-expand-collapse{
+        transform: rotateZ(0deg);
+        transition: all 0.1s linear;
+      }
+    }
+    .is-opened{
+      .custem-expand-collapse{
+        transform: rotateZ(90deg);
+      }
+    }
+    .el-menu-item  {
+      *{
+        vertical-align: middle;
+      }
+    }
+    .el-menu--collapse {
+      width: 70px;
+      height: 100%;
+      box-sizing: border-box;
+      background: #ffffff !important;
+      .el-submenu__title{
+        visibility: hidden;
+      }
+      a{
+        span{
+          visibility: hidden;
+        }
+      }
+    }
+  }
+  .el-menu-item.is-active {
+    background-color: $themeColor!important;
+    a{
+      span{
+        color: white !important;
+      }
+    }
+  }
+  .el-menu-item:hover{
+    background-color: rgba($color: $themeColor, $alpha: 0.2);
+  }
+</style>

+ 101 - 0
src/layout/sideBar/sidebarItem.vue

@@ -0,0 +1,101 @@
+<script setup>
+  import { ArrowRight } from '@element-plus/icons-vue';
+  
+  const props=defineProps({
+    item:{
+      type:Object,
+      required:true
+    },
+    basePath:{
+      type:String,
+      required:true
+    },
+    active:{
+      type:String,
+      required:true
+    },
+    isCollapse:{
+      type:Boolean,
+      required:true
+    }
+  })
+
+const resolvePath=(path)=>{
+  if(props.basePath == path){
+    return path
+  }else{
+    return props.basePath+'/'+path
+  }
+}
+
+</script>
+
+<template>
+    <template v-if="!item.hidden">
+      <template v-if="item.meta.type">
+        <!-- 一级菜单 -->
+        <template v-if="item.meta.type=='menu'"> 
+          <el-menu-item :path="item.path+'/index'" :index="item.path+'/index'" 
+          :key="item.path+'/index'" :style="!isCollapse?'padding-left:20px;text-align:left':''">
+            <template #title><span>{{item.meta.title}}</span></template> 
+            <a :href="`${item.path}/index`" 
+            :style="`display: block;color:${item.path+'/index'===active ? '#fff ' : '#666'}`" 
+            @click="(e) => e.preventDefault() ">
+              <el-icon size="24px" style="margin-right: 10px;">
+                <svg-Icon v-if="item.meta.icon_path.startsWith('svgIcon')" :name="item.meta.icon_path"
+                :color="item.path+'/index'===active?'#fff':'#666'" />
+                <component :is="item.meta.icon_path" v-else />
+              </el-icon>
+            </a>
+          </el-menu-item>
+        </template>
+        <!-- 目录 -->
+        <template v-else>
+          <el-sub-menu :index="resolvePath(item.path)" >
+            <template #title>
+              <el-icon size="24px" style="margin-right: 10px;" 
+              :style="{'color':item.children.some(child => resolvePath(child.path)===active)?'var(--themeColor)':'#666'}">
+                <svg-Icon v-if="item.meta.icon_path.startsWith('svgIcon')" :name="item.meta.icon_path"
+                :color="item.children.some(child => resolvePath(child.path)===active)?'var(--themeColor)':'#666'" />
+                <component :is="item.meta.icon_path" v-else/>
+              </el-icon>
+              <span 
+                :style="{'color':item.children.some(child => resolvePath(child.path)==active)?'#333':'#666'}"
+                class="sub-menu-title"
+              >{{item.meta.title}}</span>
+              <el-icon size="16px" class="custem-expand-collapse" style="width:16px;" 
+              :style="isCollapse?'visibility:hidden':''">
+                <ArrowRight />
+              </el-icon>
+            </template>
+            <sidebarItem v-for="routeItem in item.children" :key="routeItem.path" 
+             :item="routeItem" :base-path="resolvePath(routeItem.path)" :active="active" :isCollapse="isCollapse"/>
+          </el-sub-menu>
+        </template>
+      </template>
+      <!-- 不是一级菜单,在目录下的菜单 -->
+      <template v-else>
+        <el-menu-item
+          :index="basePath">
+          <a :href="basePath" 
+          :style="`display: block;color:${basePath===active ? '#fff ' : '#666'}`" @click="(e) => e.preventDefault() ">
+          <!-- <el-icon size="24px" style="margin-right: 10px;">
+            <component :is="item.meta.icon_path"/>
+          </el-icon> -->
+            <span>{{item.meta.title}}</span>
+          </a>
+        </el-menu-item>
+      </template>
+    </template>
+</template>
+  
+  
+<style lang="scss" scoped>
+  .sub-menu-title{
+    display:inline-block; 
+    width:96px; 
+    text-align:left;
+    box-sizing:border-box;
+    font-size:14px;
+  }
+</style>

+ 37 - 0
src/main.js

@@ -0,0 +1,37 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+// 路由
+import router from '@/router'
+// 状态管理
+import store from "@/store"
+// 全局样式
+import '@/styles/main.scss'
+// 路由守卫
+import './permission'
+// 引入图标库
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+console.log(router.getRoutes());
+const app = createApp(App)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+// 注册全局指令
+import * as directives from '@/directives'
+console.log(directives,'directives');
+Object.keys(directives).forEach(key =>{
+  app.directive(key,directives[key])
+})
+// 分页器
+import mPage from "@/components/mPage.vue"
+
+app.component('mPage',mPage)
+
+// svg图标组件
+import svgIcon from "@/components/SvgIcon/index.vue";
+//引入注册脚本
+import 'virtual:svg-icons-register'
+app.component('svg-icon', svgIcon)
+
+app.use(router)
+app.use(store)
+app.mount('#app')

+ 58 - 0
src/permission.js

@@ -0,0 +1,58 @@
+import router from "./router"
+import store from "./store"
+
+import NProgress from "nprogress"
+import 'nprogress/nprogress.css'
+
+NProgress.configure({  //进度条设置
+  easing:'ease',
+  speed:500,
+  showSpinner:false
+})
+
+const whileList=['/login']
+
+router.beforeEach(async (to,from,next)=>{
+  // 进度条开始
+  NProgress.start()
+
+  const hasToken = localStorage.getItem('fsms_token')
+  // console.log(hasToken);
+  if(hasToken){
+    // console.log(store.getters.routes.length,store.getters.permissionButtons.length);
+    if(to.path=='/login'){
+      // 有token的情况下访问login,由于不重定向到主页,所以需要重新设置路由和权限
+      if(store.getters.routes.length){
+        // 存有之前的路由数组
+        location.reload() // 刷新页面,重置路由数组和权限按钮数据
+      }else{
+        // 没有之前的路由数据,放行
+        next()
+        NProgress.done()
+        return 
+      }
+    }
+    if(store.getters.permissionButtons.length == 0){
+      // 获取权限按钮
+      await store.dispatch('router/getPermissionButtons') 
+    }
+    if(store.getters.routes.length && to.name){
+      console.log('放行');
+      next()  
+      NProgress.done()
+    }else{
+      console.log('拦截');
+      await store.dispatch('router/setRoutes')
+      next({...to,replace:true})
+    }
+  }else{
+      if(whileList.indexOf(to.path) !== -1){
+          next()
+          NProgress.done()
+      }else{
+          // 不在访问白名单 需要登录
+          next('/login')
+      }
+  }
+
+})

+ 50 - 0
src/router/index.js

@@ -0,0 +1,50 @@
+import {createRouter,createWebHistory} from "vue-router"
+import Layout from "@/layout/index.vue"
+
+export const routes=[
+  {
+    path:'/login',
+    name:'Login',
+    component:() => import("@/views/Login.vue"),
+    hidden:true
+  },
+  {
+    path:'/',
+    name:'home',
+    redirect:'/financial/list',
+    hidden:true
+  },
+  {
+    path:'/404',
+    name:'404',
+    component:()=>import('@/views/404.vue'),
+    hidden:true
+  },
+  {
+    path:'/changePassword',
+    name:'修改密码',
+    component:Layout,
+    hidden:true,
+    redirect:'/changePassword/index',
+    children:[
+      {
+        path:'index',
+        name:'changePassword',
+        component:() => import("@/views/ChangePassword.vue"),
+        hidden:true,
+        meta:{
+          title:'修改密码',
+          icon_path:''
+        }
+      }
+    ]
+  }
+]
+
+const router = createRouter({
+  history:createWebHistory(),
+  // history:createWebHashHistory(),
+  routes
+})
+
+export default router

+ 7 - 0
src/store/getters.js

@@ -0,0 +1,7 @@
+const getters={
+  token:state => state.user.token,
+  routes:state => state.router.routes,
+  permissionButtons:state => state.router.permissionButtons
+}
+
+export default getters

+ 17 - 0
src/store/index.js

@@ -0,0 +1,17 @@
+import {createStore} from 'vuex'
+import getters from "./getters"
+
+const modulesFiles = import.meta.globEager('./modules/*.js')
+// 批量导入文件
+const modules = Object.keys(modulesFiles).reduce((modules,modulePath)=>{
+  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/,'$1').split('/')[1]
+  modules[moduleName] = modulesFiles[modulePath].default
+  return modules
+},{})
+
+const store = createStore({
+  modules,
+  getters
+})
+
+export default store

+ 116 - 0
src/store/modules/router.js

@@ -0,0 +1,116 @@
+import {routes} from '../../router'
+import router from '../../router'
+import {getAsyncRoutes,getPermissionButtons} from '@/api/systemMana'
+import Layout from "@/layout/index.vue"
+
+const hiddenArray=[false,true]
+// 动态引入views中所有的组件
+const routeAllPathToCompMap = import.meta.glob(`@/views/**/*.vue`)
+// console.log(routeAllPathToCompMap);
+
+const routeHandle=(route,path='index')=>{
+  path = path.startsWith('/')?path.substring(1):path
+  if(route.component){
+    // 一级菜单
+    return {
+      path,
+      name:route.level==0?`${route.path_name}Index`:route.path_name,
+      component:routeAllPathToCompMap[`/src/${route.component}.vue`],
+      hidden:hiddenArray[route.hidden],
+      meta:{
+        title:route.name,
+        icon_path:route.icon_path
+      },
+      children:(route.children && route.children.length>0)?route.children.map(item => routeHandle(item,item.path)):[]
+    }
+  }else{
+    if(route.children && route.children.length>0){
+      route.children = route.children.map(item=>{
+        return routeHandle(item,item.path)
+      })
+      return route.children
+    }
+  }
+}
+
+export default {
+  state:{
+    routes:[],
+    permissionButtons:[]
+  },
+  mutations:{
+    SET_ROUTES:(state,routes)=>{
+      state.routes = routes
+    },
+    GET_PERMISSION_BUTTONS:(state,buttons)=>{
+      state.permissionButtons = buttons
+    }
+  },
+  actions:{
+    // 设置路由
+    setRoutes({commit}){
+      return new Promise((resolve,reject)=>{
+        let asyncRoutes = []
+        // console.log(router.getRoutes(),'router.getRoutes()');
+        getAsyncRoutes().then(res=>{
+          let routesData = res.data.list || []
+          routesData.map(item=>{
+            let afterHandlePath = item.path.startsWith('/')?item.path:'/'+item.path
+            if(item.hidden_layout==0){
+              let afterHandle = routeHandle(Object.assign({},item))
+              // console.log(afterHandle);
+              // 判断 afterHandle 是不是数组
+              let flag=true
+              if(!Array.isArray(afterHandle)){
+                flag=false
+              }
+              asyncRoutes.push({
+                path:afterHandlePath,
+                name:item.path_name,
+                component:Layout,
+                hidden:hiddenArray[item.hidden],
+                redirect:item.component?`${afterHandlePath}/index`:`${afterHandlePath}/${item.children[0]?item.children[0].path:''}`,
+                meta:{
+                  title:item.name,
+                  icon_path:item.icon_path,
+                  type:!flag?'menu':'content'
+                },
+                children:!flag?[afterHandle]:afterHandle
+              })
+            }else{
+              asyncRoutes.push({
+                path:afterHandlePath,
+                name:item.path_name,
+                component:routeAllPathToCompMap[`/src/${item.component}.vue`],
+                hidden:hiddenArray[item.hidden],
+                meta:{
+                  title:item.name,
+                  icon_path:item.icon_path,
+                  type:'menu'
+                }     
+              })
+            }
+          })
+          asyncRoutes.push({ path: '/:catchAll(.*)', redirect: '/404',hidden:true})
+          asyncRoutes.map(item =>{
+            router.addRoute(item)
+          })
+          // console.log(router.getRoutes(),'router.getRoutes()');
+          commit('SET_ROUTES',[...routes ,...asyncRoutes])
+          resolve('设置路由成功')
+        })
+      })
+    },
+    // 获取权限按钮
+    getPermissionButtons({commit}){
+      return new Promise((resolve,reject)=>{
+        getPermissionButtons().then(res=>{
+          commit('GET_PERMISSION_BUTTONS',res.data.list || [])
+          // console.log(res);
+          resolve('获取权限按钮成功')
+        })
+      })
+    }
+  },
+  namespaced:true
+}

+ 14 - 0
src/store/modules/user.js

@@ -0,0 +1,14 @@
+export default {
+  state:{
+    token:localStorage.getItem('fsms_token')
+  },
+  mutations:{
+
+  },
+  actions:{
+    getToken(){
+      return state.token
+    },
+  },
+  namespaced:true
+}

+ 9 - 0
src/styles/element.scss

@@ -0,0 +1,9 @@
+
+// 重新定义element-plus主题
+@forward 'element-plus/theme-chalk/src/common/var.scss' with (
+  $colors: (
+    'primary': (
+      'base': #AB9523,
+    ),
+  ),
+);

+ 189 - 0
src/styles/main.scss

@@ -0,0 +1,189 @@
+// @import './theme.scss';
+/**
+* $themeColor 主题颜色
+*/
+body,div,p,span,input,textarea,h1,h2,h3,h4,h5,h6,select,option,ul,ol,li,table,thead,tbody,th,tr,td,hr,blockquote,dl,dt,dd,button{
+  margin: 0;
+  padding: 0;
+}
+html{
+  // css 变量
+  --themeColor:#{$themeColor};
+}
+
+
+html,
+body,
+#app {
+  width: 100vw;
+  height: 100vh;
+  font-size: 16px;
+}
+a:hover,a{
+  text-decoration: none;
+}
+// 内容区域头部按钮
+.header-optios-buttons{
+  width: 130px!important;
+  height: 40px!important;
+  border-radius: 4px !important;
+  span{
+    font-size: 16px;
+  }
+}
+// 修改原生滚动条样式
+div::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+div::-webkit-scrollbar-track {
+  background: rgb(239, 239, 239);
+  border-radius: 2px;
+}
+div::-webkit-scrollbar-thumb {
+  background: #ccc;
+  border-radius: 10px;
+}
+div::-webkit-scrollbar-thumb:hover {
+  background: #888;
+}
+div::-webkit-scrollbar-corner {
+  background: #666;
+}
+
+// 表格操作栏--筛选列表列
+.table-column-select{
+  display: flex;
+  align-items: center;
+}
+// 表格操作栏
+.table-options{
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  // 操作栏按钮
+  .table-option-buttons{
+    cursor: pointer;
+    color: $themeColor;
+    margin-right:10px;
+    white-space: nowrap;
+  }
+}
+
+// 表格没有数据的插槽样式
+.table-no-data{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 70px 0 40px 0;
+  img{
+    height: 80px;
+    width: 80px;
+  }
+  span{
+    font-size: 14px;
+    color: #999;
+  }
+}
+// element-plus 样式修改 ----------------------------
+
+.el-popper,.el-upload-list__item{
+  outline: none;
+}
+// 联级选择器的icon样式调整
+.el-cascader__dropdown{
+  .el-icon{
+    position: absolute;
+  }
+}
+.el-input__wrapper{
+  height: 40px;
+  box-sizing: border-box;
+  padding: 0 12px;
+}
+// 时间选择器高度
+.el-date-editor.el-input, .el-date-editor.el-input__wrapper{
+  height: 40px!important;
+}
+
+// 表格头部
+.el-table__header{
+  .cell{
+    font-weight: bold;
+    color: #000000;
+  }
+}
+
+// 弹窗头部
+.el-dialog__header{
+  border-bottom: 1px solid #E4E7ED;
+  margin-right:0!important;
+  padding: 15px!important;
+  .el-dialog__headerbtn{
+    width: 48px;
+    height: 48px;
+  }
+  span{
+    font-size: 18px;
+    // font-family: PingFang SC-Regular, PingFang SC;
+    font-weight: 400;
+    color: #333333;
+  }
+}
+// messageBox 头部
+.el-message-box__header{
+  border-bottom: 1px solid #E4E7ED;
+  padding-bottom: 15px!important;
+}
+// messageBox 按钮区
+.el-message-box__btns{
+  button{
+    width: 80px;
+  }
+  button:first-child{
+    border: 1px solid $themeColor;
+    color: $themeColor;
+  }
+  button:nth-child(2){
+    margin-left: 20px!important;
+  }
+}
+// 弹窗尾部 
+.el-dialog__footer{
+  text-align: center!important;
+  padding-top: 20px!important;
+  button{
+    height: 36px;
+    width: 120px;
+    font-size: 16px;
+    // font-family: PingFang SC-Medium, PingFang SC;
+    font-weight: 500;
+    &:first-child{
+      border: 1px solid $themeColor;
+      color: $themeColor;
+    }
+  }
+  .el-button + .el-button{
+    margin-left: 20px;
+  }
+}
+// form
+.el-form-item{
+  display: flex;
+  align-items: center;
+}
+// form - label
+.el-form-item__label{
+  padding: 0 20px 0 0!important;
+}
+// element-plus label自定义样式 -- 文字等分对其
+.label-custom-equally{
+  text-align: justify;
+  text-justify: inter-ideograph;//IE兼容
+  width: 100%;
+  &::after{
+    content: '';
+    display: inline-block;
+    width: 100%;
+  }
+}

+ 2 - 0
src/styles/theme.scss

@@ -0,0 +1,2 @@
+// 主题颜色
+$themeColor:#AB9523;

+ 66 - 0
src/utils/request.js

@@ -0,0 +1,66 @@
+import axios from "axios";
+import { ElMessage } from 'element-plus'
+
+// 创建一个axios实例
+const request = axios.create({
+  baseURL: import.meta.env.VITE_APP_API_URL,
+  timeout:20000,  //请求超时时间 20秒
+
+})
+
+request.interceptors.request.use(
+  config=>{
+    // console.log(config);
+    config.headers['Authorization'] = localStorage.getItem('fsms_token') || ''
+    return config
+  },
+  err=>{
+    console.log(err);
+    return Promise.reject(err)
+  }
+)
+
+request.interceptors.response.use(
+  res=>{
+    if(res.request.responseType==='blob' || res.request.responseType==='Blob'){
+       // 二进制流
+       if(res.status === 200){
+        return res.data
+       }
+    }else if(res.data.code==200){
+      return res.data
+    }else if(res.data.code==4010 || res.data.code==401){
+      // 4010 禁用操作 401 token过期
+      ElMessage({
+        message:res.data.msg || '未知错误,请重新登录',
+        type:'error',
+        duration:1500
+      })
+      localStorage.removeItem('fsms_token')
+      localStorage.removeItem('userInfo')
+      setTimeout(()=>{
+        location.reload()
+      },1500)
+      return Promise.reject(new Error(res.data.msg ||'未知错误,请重新登录'))
+    }else{
+      ElMessage({
+        message:(res.data && res.data.msg) ? res.data.msg : '网络异常',
+        type:'error',
+        duration:3*1000
+      })
+      return Promise.reject(new Error(res.data.msg ||'网络异常'))
+    }
+  },
+  err=>{
+      const {message} = err
+      console.log(message);
+      ElMessage({
+        message: message || '网络异常',
+        type: 'error',
+        duration: 3 * 1000
+      })
+      return Promise.reject(err)
+  }
+)
+
+export default request

+ 13 - 0
src/utils/validators.js

@@ -0,0 +1,13 @@
+/*
+* 表单自定义验证规则
+*/
+
+// 邮箱格式
+export const emailValid=(rule,value,callback)=>{
+  let reg = /^[a-zA-Z0-9]+([-_.][A-Za-z\d]+)*@([a-zA-Z\d]+[-.])+[A-Za-z\d]{2,5}$/
+  if(value.match(reg)){
+    callback()
+  }else{
+    callback(new Error('邮箱格式错误,请重试。'))
+  }
+}

+ 7 - 0
src/views/404.vue

@@ -0,0 +1,7 @@
+<template>
+    <div id="gifimg"></div>
+</template>
+
+<style lang="scss" scoped>
+    #gifimg{ width:100%; height:100%; background:#43A0B6 url("https://hzstatic.hzinsights.com/hrms/404.gif") no-repeat center center; }
+</style>

+ 121 - 0
src/views/ChangePassword.vue

@@ -0,0 +1,121 @@
+<script setup>
+import { ElMessage } from 'element-plus';
+import md5 from 'js-md5'
+import {changeSelfPsd} from '@/api/user'
+
+  const changePsd=reactive({
+    addForm:{
+      OldPwd:'',
+      NewPwd:'',
+      twoNewPwd:''
+    },
+    rules: {
+      OldPwd: [{
+        required:true,
+        message:'请输入原密码',
+        trigger:'blur'
+      }],
+      NewPwd: [{
+        required:true,
+        message:'请输入新密码',
+        trigger:'blur'
+      }],
+      twoNewPwd: [{
+        required:true,
+        message:'请输入确认密码',
+        trigger:'blur'
+      }]
+    }
+  })
+  const addForm = ref(null)
+
+  //  -------------------method
+  const historyBack=()=>{  //返回上一页
+      history.back();
+  }
+  const addSubmit=()=>{
+    addForm.value.validate((valid) => {
+      if(valid){
+        if( changePsd.addForm.NewPwd!=changePsd.addForm.twoNewPwd ){
+          ElMessage({
+            message:'新密码两次输入不一致,请核对!',
+            type:'warning'
+          })
+          return false;
+        }
+        let params={
+          old_pwd:md5(changePsd.addForm.OldPwd),
+          new_pwd:md5(changePsd.addForm.NewPwd),
+          confirm_pwd:md5(changePsd.addForm.twoNewPwd)
+        }
+        changeSelfPsd(params).then(res=>{
+          localStorage.removeItem('fsms_token')
+          localStorage.removeItem('userInfo')
+          localStorage.removeItem('account')
+	        localStorage.removeItem('checkPass')
+          ElMessage.success('修改密码成功,请重新登录!')
+          setTimeout(()=>{
+            location.reload()
+          },1500)
+        })
+      }
+    })
+  }
+</script>
+
+<template>
+	<div class="changePsd-container" id="changePsd-container">
+    <b class="changePsd-header">修改密码</b>
+    <div class="box-card">
+      <el-form :model="changePsd.addForm" :rules="changePsd.rules" label-width="102px" ref="addForm" style="width:500px;">
+        <el-form-item label="原密码" prop="OldPwd">
+          <el-input type="text" v-model="changePsd.addForm.OldPwd" placeholder="请输入不超过20个字符" 
+          clearable autocomplete="new-password" style="width:356px"></el-input>
+        </el-form-item>
+        <el-form-item label="新密码" prop="NewPwd">
+          <el-input type="password" v-model="changePsd.addForm.NewPwd" placeholder="请输入长度不超过20个字符" 
+          maxlength="20" show-password style="width:356px">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="确认新密码" prop="twoNewPwd">
+          <el-input type="password" v-model="changePsd.addForm.twoNewPwd" placeholder="请输入长度不超过20个字符" 
+          maxlength="20" show-password style="width:356px">
+          </el-input>
+        </el-form-item>
+        <el-form-item style="text-align:center; padding-top:30px;">
+          <el-button type="primary" size="medium" @click.native="addSubmit" style="width: 120px;height: 36px;">确定</el-button>
+          <el-button size="medium" @click.native="historyBack" 
+          style="margin-left: 20px;width: 120px;height: 36px;border: 1px solid var(--themeColor);color: var(--themeColor);">返回</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+  
+<style lang="scss" scoped>
+  .changePsd-container{
+    .changePsd-header{
+      font-size: 16px;
+      // font-family: PingFang SC-Medium, PingFang SC;
+      color: #333333;
+      margin-bottom: 20px;
+      display: block;
+    }
+    .box-card{
+      height: 440px;
+      width: 900px;
+      padding: 40px 30px;
+      box-sizing: border-box;
+      background-color: white;
+      border-radius: 8px;
+    }
+  }
+</style>
+<style lang="scss">
+  #changePsd-container{
+    .el-form-item{
+      margin-bottom: 30px;
+    }
+  }
+
+</style>

+ 244 - 0
src/views/Login.vue

@@ -0,0 +1,244 @@
+<script setup>
+	import {useRouter} from 'vue-router'
+	import md5 from 'js-md5'
+	import { Base64 } from 'js-base64';
+	import {authLogin} from '@/api/user'
+
+	const router = useRouter()
+
+	let ruleForm = ref(null)
+	const login=reactive({
+		ruleForm:{
+			account: '',
+			checkPass: '',
+		},
+		rules: {
+			account: [
+				{
+					required: true,
+					message: '请输入用户名',
+					trigger: 'blur',
+				},
+			],
+			checkPass: [
+				{
+					required: true,
+					message: '请输入密码',
+					trigger: 'blur',
+				},
+			],
+		},
+		logining:false,
+		checked: false, //是否保持登录状态
+	})
+	// 登录操作
+	const handleSubmit=()=>{
+		ruleForm.value.validate((vaild)=>{
+			if(vaild){
+				let params = {
+					admin_name:login.ruleForm.account,
+					password:md5(login.ruleForm.checkPass),
+					is_remember:login.checked
+				}
+				authLogin(params).then(async ({data})=>{
+					let {admin_name,change_pwd,real_name,token} = data
+					localStorage.setItem('fsms_token',token)
+					localStorage.setItem('userInfo',JSON.stringify({
+						admin_name:admin_name,
+						change_pwd:change_pwd,
+						real_name:real_name,
+					}))
+					if(login.checked){
+						localStorage.setItem('account',Base64.encode(login.ruleForm.account))
+						localStorage.setItem('checkPass',Base64.encode(login.ruleForm.checkPass))
+					}
+					router.replace('/')
+				})
+			}
+		})
+	}
+	const keyDownLoad=(e)=>{
+		if(e.keyCode == 13){
+			// enter键
+			handleSubmit()
+		}
+	}
+	onMounted(()=>{
+		window.addEventListener('keydown',keyDownLoad)
+	})
+	onUnmounted(()=>{
+		window.removeEventListener('keydown',keyDownLoad)
+	})
+
+//  ---------------------created
+let userAccount = localStorage.getItem('account') || null
+let userCheckPass = localStorage.getItem('checkPass') || null
+if (userAccount) {
+	login.ruleForm.account = Base64.decode(userAccount)
+	login.ruleForm.checkPass = Base64.decode(userCheckPass)
+	login.checked = true
+	localStorage.removeItem('account')
+	localStorage.removeItem('checkPass')
+}
+</script>
+
+<template>
+	<div id="login_container">
+		<div class="login_leftCon">
+			<div class="login_form">		
+				<h1>
+					弘则研究财务报表系统
+				</h1>
+				<p>Welcom to login.</p>
+				<el-form
+					:model="login.ruleForm"
+					:rules="login.rules"
+					ref="ruleForm"
+					label-position="right"
+					label-width="0px"
+					class="demo-ruleForm"
+					id="login-container"
+					@submit.native.prevent
+				>
+					<el-form-item prop="account" style="margin-bottom: 40px;">
+						<el-input
+							type="text"
+							v-model="login.ruleForm.account"
+							auto-complete="off"
+							placeholder="请输入用户名"
+						/>
+					</el-form-item>
+					<el-form-item prop="checkPass" style="margin-bottom: 30px;">
+						<el-input
+							type="password"
+							v-model="login.ruleForm.checkPass"
+							auto-complete="off"
+							placeholder="请输入密码"
+						/>
+					</el-form-item>
+					<div class="login-remember-status">
+						<el-checkbox v-model="login.checked" class="remember"
+						>保持登录状态</el-checkbox>
+						<div class="warn-check-tip" v-show="login.checked">
+							<el-icon color="#B72E18" ><WarningFilled /></el-icon>
+							勾选表示信任此设备,系统将不会自动退出该设备,使用公用电脑请勿勾选!
+						</div>
+					</div>
+
+					<el-form-item>
+						<el-button
+							type="primary"
+							@click="handleSubmit"
+							:loading="login.logining"
+							class="submit_btn"
+							>登录</el-button
+						>
+					</el-form-item>
+					<p class="password-hint">忘记密码请联系管理员</p>
+				</el-form>
+			</div>
+		</div>
+		<div class="login_rightCon">
+			<img src="https://hzstatic.hzinsights.com/static/fms/imgs/financial-login-right.jpg">
+		</div>
+	</div>
+</template>
+  
+<style lang="scss" scoped>
+	@import '@/styles/theme.scss';
+	#login_container{
+		display: flex;
+		height: 100%;
+		box-sizing: border-box;
+		.login_leftCon{
+			flex-grow: 1;
+			display: flex;
+			justify-content: center;
+			padding-top: 20vh;
+			.login_form{
+				width:29.6vw;
+				min-width: 360px;
+				margin: 0 100px;
+				h1{
+					font-size: 44px;
+					// font-family: PingFang SC-Medium, PingFang SC;
+					font-weight: 500;
+					color: #333333;
+					margin-bottom: 24px;
+				}
+				@media screen and (max-width:1680px) {
+					h1{
+						font-size: 36px;
+					}
+				}
+				@media screen and (max-width:1400px) {
+					h1{
+						font-size: 30px;
+					}
+				}
+				p{
+					font-size: 20px;
+					// font-family: PingFang SC-Regular, PingFang SC;
+					font-weight: 400;
+					color: #999CB0;
+					margin-bottom: 48px;
+				}
+				.login-remember-status{
+					position: relative;
+					.remember {
+						margin: 0 0 64px 0;
+					}
+					.warn-check-tip {
+						position: absolute;
+						min-width: 300px;
+						left: 130px;
+						top: 0;
+						padding: 2px 10px;
+						border: 1px solid #D1433A;
+						background: #FFEAE9;
+						color: #B72E18;
+						font-size: 14px;
+					}
+				}
+
+				.submit_btn {
+					width: 100%;
+					height: 60px;
+					font-size: 24px;
+					border-radius: 8px;
+				}
+				.password-hint{
+					// font-family: 'PingFang SC';
+					margin-top: 36px;
+					font-size: 20px;
+					color: #999CB0;
+				}
+			}
+		}
+		.login_rightCon{
+			img{
+				height: 100vh;
+				min-width: 300px;
+			}
+		}
+	}
+</style>
+<style lang="scss">
+	#login_container{
+		.el-input__wrapper{
+			height: 48px;
+			padding: 0 24px;
+		}
+		.el-input__inner{
+			font-size: 18px;
+		}
+		.el-form-item__error{
+			font-size: 16px;
+		}
+		.remember {
+			.el-checkbox__label{
+				font-size: 16px;
+			}
+		}
+	}
+</style>

+ 671 - 0
src/views/financialManagement/components/serviceVarietyDia.vue

@@ -0,0 +1,671 @@
+<script setup>
+
+import {getPermissionList} from '@/api/crm'
+import html2canvas from "html2canvas";
+
+  const props=defineProps({
+    visible:{
+      type:Boolean,
+      default:false
+    },
+    service:{
+      type:Object,
+      require:true
+    },
+    currentService:{
+      type:Object,
+      default:()=>{}
+    }
+  })
+
+  const headTitleForm=ref(null)
+  
+  const params=reactive({
+    tableHeadData:[],
+    tableData:[],
+    showRightClickMenu:false,//显示右键菜单
+    rightClickCon: [
+      { type: "selectRow", name: "选择该行" },
+      { type: "insertColumn", name: "插入一列" },
+      { type: "insertRow", name: "插入一行" },
+      { type: "deleteColumn", name: "删除该列" },
+      { type: "deleteRow", name: "删除该行" },
+    ], //右键菜单内容
+    showInputHead: false, //显示填写表头
+    form: {
+      title: "",
+    }, //插入一列填写的表头数据
+    tableSelectList: {
+      商品复盘: [
+        { id: 2, name: "能化专栏《化里化外》", select: false },
+        { id: 4, name: "黑色专栏《知白守黑》", select: false },
+        { id: 5, name: "有色专栏《有声有色》", select: false },
+        // { id: 1, name: "《每日宏观商品复盘》", select: false },
+        { id: 3, name: "《股债日评》", select: false },
+      ],
+      深度月报:[
+        { id: 6, name: "宏观经济", select: false },
+        { id: 7, name: "草根调研", select: false },
+        // { id: 8, name: "PVC月报", select: false },
+      ]
+    }, //表格中自带选择的数据
+    tableSelectCheck: {
+      商品复盘: [],
+    }, //表格中自带选择的数据选中项
+    selectRowIndex:'',
+    selectIndex:{
+      rindex: 0, //行序号
+      cindex: 0, //列序号
+    },
+    position: {
+      top: 0,
+      left: 0,
+    }, //鼠标右击时的位置
+  })
+
+  const varietyDia=reactive({
+    dialogShow:false,
+    varietyList:[],
+    // 选中的Id
+    chart_permission_ids:''
+  })
+
+  const goodsArr=["能化专栏《化里化外》","黑色专栏《知白守黑》","有色专栏《有声有色》","《股债日评》"]
+  const monthReport=["宏观经济","草根调研"]
+  const emits=defineEmits(['update:visible','selectFinish'])
+
+  watch(()=>props.service,(newValue)=>{
+    // console.log(newValue,'value---');
+    if(newValue) initData()
+  },{deep:true})
+  watch(()=>params.selectRowIndex,(newValue)=>{
+    if (newValue === null) {
+      varietyDia.varietyList.forEach((item) => {
+        item.check_list = [];
+      });
+    }
+  })
+  watch(()=>props.currentService,(newVal)=>{
+    // console.log(newVal,'newVal------------------');
+    if (newVal.service_template_id) {
+      if (props.service.service_template_id === newVal.service_template_id) {
+        props.service.Value = newVal.Value;
+        // 表格数据 newVal 更新的数据
+        // 更新表头 props.service.detail[0]
+        let arr=[newVal.tableHeadData,...newVal.tableData].map((item)=>{
+          let obj={}
+          item.forEach((item2,index2)=>{
+            let key=`col_${index2+1}`
+            obj={...obj,[key]:JSON.stringify(item2)}
+          })
+          return obj
+        })
+        props.service.detail=arr
+      }
+    }
+  })
+// --------------method
+// 获取合同种类接口
+const getPermissionListFun=()=>{
+  getPermissionList().then(res=>{
+    let arr = res.data.List || []
+    arr.forEach((item) => {
+      item.check_list = [];
+      item.checked = false;
+      item.indeterminate = false;
+    });
+    varietyDia.varietyList=arr
+  })
+}
+// 初始化表格数据
+const initData=()=>{
+  if (!props.service.detail) return;
+  // console.log(props.service.detail);
+  let temarr = props.service.detail.map((rowItem) => {
+    let rowArr = [];
+    for (let key in rowItem) {
+      if (key.substr(0, 3) === "col" && rowItem[key] !== "") {
+        rowArr.push(JSON.parse(rowItem[key]));
+      }
+    }
+    return rowArr;
+  });
+  params.tableHeadData = temarr[0];
+  params.tableData = temarr.slice(1);
+  // 回显表格内部选择的
+  params.tableData.forEach((item) => {
+    item.forEach((e) => {
+      if (e.Type === "select") {
+        let key = e.Value;
+        item.forEach((e2) => {
+          if (e2.HeadName === "品种") {
+            if(e2.Value){
+              params.tableSelectCheck[key] = e2.ValueId;
+            }else{
+              params.tableSelectCheck[key] = [];
+            }
+          }
+        });
+      }
+    });
+  });
+}
+// 左键选择行
+const handleSelectRow=(rindex,data)=> {
+  // 如果选择的行存在 选项数据则不允许选择 或者 该行不可选择 即RowDisable=true
+  if(data.HeadName!='品种') return
+  let tag = false;
+  params.tableData[rindex].forEach((item) => {
+    if (item.Type === "select"||item.RowDisable) {
+      tag = true;
+    }
+  });
+  if (tag) return;
+  params.selectRowIndex = rindex;
+  formatOptionList();
+  varietyDia.dialogShow=true
+}
+
+//每次选中一行时格式化顶部选择数据
+const formatOptionList=()=>{
+  let valueId = [];
+  params.tableData[params.selectRowIndex].forEach((item) => {
+    if (item.HeadName === "品种") {
+      valueId = item.ValueId;
+    }
+  });
+  varietyDia.varietyList.forEach((item) => {
+    item.check_list = [];
+    item.items.forEach((e) => {
+      let flag = valueId.indexOf(e.chart_permission_id);
+      if (flag != -1) {
+        item.check_list.push(e.chart_permission_id);
+      }
+    });
+    if (item.check_list.length === item.items.length) {
+      item.checked = true;
+    } else {
+      item.checked = false;
+    }
+  });
+}
+
+//鼠标右键
+const rightClick=(rindex, cindex, event)=>{
+  params.selectIndex.rindex = Number(rindex);
+  params.selectIndex.cindex = Number(cindex);
+  formatRightClickCon(rindex, cindex);
+  params.showRightClickMenu = true;
+  params.position.top = event.clientY + "px";
+  params.position.left = event.clientX + "px";
+}
+//格式化鼠标右击显示内容
+const formatRightClickCon=(rindex, cindex)=>{
+  let arr = [
+    { type: "insertColumn", name: "向后插入一列", show: true },
+    { type: "insertRow", name: "向后插入一行", show: true },
+    { type: "deleteColumn", name: "删除该列", show: true },
+    { type: "deleteRow", name: "删除该行", show: true },
+  ];
+
+  // 品种一列不允许删除
+  if (params.tableHeadData[cindex].Tag === "品种"||params.tableHeadData[cindex].Tag === "FICC小套餐服务内容") {
+    arr[2].show = false;
+  }
+  //删除列 不能少于两列
+  if (params.tableHeadData.length <= 2) {
+    arr[2].show = false;
+  }
+
+  //删除行 不能少于两行 并且商品复盘、周报、双周报、数据点评、月报必须有一个存在
+  if (params.tableData.length <= 2) {
+    arr[3].show = false;
+  }
+
+  let temarr=[]
+  params.tableData.forEach(item=>{
+    item.forEach(item2=>{
+      if(item2.HeadName==='FICC小套餐服务内容'){
+        if(item2.Value==='数据点评'||
+        item2.Value==='FICC周报'||
+        item2.Value==='商品双周报+线上电话会讨论会<br/>(由弘则的研究员主持线上讨论)'){
+          temarr.push(item2.Value)
+        }
+      }
+    })
+  })
+  if(temarr.length<2){
+    params.tableData[params.selectIndex.rindex].forEach(item=>{
+      if(item.Value==='数据点评'||
+        item.Value==='FICC周报'||
+        item.Value==='商品双周报+线上电话会讨论会<br/>(由弘则的研究员主持线上讨论)'){
+          arr[3].show = false;
+      }
+    })
+  }
+
+  //新增列 不能超过6列
+  if (params.tableHeadData.length >= 6) {
+    arr[0].show = false;
+  }
+
+  params.rightClickCon = arr.filter((item) => {
+    return item.show;
+  });
+}
+
+// 点击 新增/删除 行/列  选择该行
+const handleUpdateTable=(type)=>{
+  //删除行
+  if (type === "deleteRow") {
+    params.selectRowIndex = null;
+    params.tableData.splice(params.selectIndex.rindex, 1);
+  }
+
+  //新增行
+  if (type === "insertRow") {
+    params.selectRowIndex = null;
+    let arr = params.tableHeadData.map((item) => {
+      let obj = { CanEdit: true, Type: "text", Value: "", ValueId: [], HeadName: item.Tag,RowDisable:false,RowName:''};
+      if (item.Tag === "品种") {
+        obj.CanEdit = false;
+      }
+      return obj;
+    });
+
+    params.tableData.splice(params.selectIndex.rindex + 1, 0, arr);
+  }
+
+  //删除列
+  if (type === "deleteColumn") {
+    params.selectRowIndex = null;
+    params.tableHeadData.splice(params.selectIndex.cindex, 1);
+    params.tableData = params.tableData.map((item) => {
+      item.splice(params.selectIndex.cindex, 1);
+      return item;
+    });
+  }
+
+  //新增列
+  if (type === "insertColumn") {
+    params.selectRowIndex = null;
+    params.showInputHead = true;
+  }
+
+  params.showRightClickMenu = false;
+}
+
+//点击确定新增表头内容
+const handleConfirmHead=()=>{
+  headTitleForm.value.validate((valid) => {
+    if (!valid) return;
+    params.tableHeadData.splice(params.selectIndex.cindex + 1, 0, { CanEdit: false, Type: "text", Value: params.form.title, Tag: params.form.title });
+    params.tableData = params.tableData.map((item) => {
+      item.splice(params.selectIndex.cindex + 1, 0, { CanEdit: true, Type: "text", Value: "", HeadName: params.form.title,RowDisable:false,RowName:'' });
+      return item;
+    });
+    params.showInputHead = false;
+    params.form.title = "";
+  });
+}
+
+//点击取消填写新增列头弹窗
+const handleCancelHead=()=>{
+  headTitleForm.value.resetFields();
+  params.showInputHead = false;
+}
+
+//点击表格中选项数据
+const handleTableSelectChange=(title, rindex)=>{
+  let arr = params.tableSelectCheck[title];
+  let arr2 = [];
+  params.tableSelectList[title].forEach((item) => {
+    arr.forEach((e) => {
+      if (e == item.id) {
+        arr2.push(item.name);
+      }
+    });
+  });
+  params.tableData[rindex].forEach((item) => {
+    if (item.HeadName === "品种") {
+      item.Value = arr2.join("、");
+      item.ValueId = arr;
+    }
+  });
+}
+const varietyCheckedChange=(e)=>{
+  e.checked = e.check_list.length == e.items.length
+  e.indeterminate = e.check_list.length>0 && e.check_list.length < e.items.length
+}
+
+const varietyAllChecked=(e)=>{
+  let arr = e.items.map((item) => {
+    return item.chart_permission_id;
+  });
+  e.check_list = e.checked ? arr : [];
+  e.indeterminate = false;
+  // 需求更改,合同类型为补充协议时不默认勾选宏观经济
+  if(e.classify_name==='宏观经济'){
+    e.check_list.push(1)
+  }
+}
+
+
+const varietiesSelect=()=>{
+  if (params.selectRowIndex === null) return;
+  let arr=[]
+  let arrId=[]
+  let count = 0
+  varietyDia.varietyList.forEach((item) => {
+    if (item.checked) {
+      count++;
+    }
+  });
+  // 全选
+  if(count===varietyDia.varietyList.length){
+    arr = ["全品种"];
+    varietyDia.varietyList.forEach((item) => {
+      arrId = [...arrId, ...item.check_list];
+    });
+  }else{
+    varietyDia.varietyList.forEach((item) => {
+      item.items.forEach((item2) => {
+        let flag = item.check_list.indexOf(item2.chart_permission_id);
+        if (flag != -1) {
+          arr.push(item2.permission_name);
+          arrId.push(item2.chart_permission_id);
+        }
+      });
+    });
+  }
+
+  // 找出和该行关联的行 同时修改品种栏数据
+  let rowIndexarr=[]
+  if(!params.tableData[params.selectRowIndex][0].RowName){
+    rowIndexarr.push(params.selectRowIndex)
+  }else{
+    params.tableData.forEach((item,index)=>{
+      if(item[0].RowName==params.tableData[params.selectRowIndex][0].RowName){
+        rowIndexarr.push(index)
+      }
+    })
+  }
+  
+
+  rowIndexarr.forEach(index=>{
+    params.tableData[index].forEach((item) => {
+      if (item.HeadName === "品种") {
+        item.Value = arr.join("、");
+        item.ValueId = arrId;
+      }
+    });
+  })
+  let idArr = []
+  varietyDia.varietyList.map(item=>{
+    idArr=[...idArr,...item.check_list]
+  })
+  varietyDia.chart_permission_ids=idArr.join(',')
+  varietyDia.dialogShow=false
+  params.selectRowIndex=null
+}
+
+const varietiesCancel=()=>{
+  varietyDia.dialogShow=false
+  params.selectRowIndex=null
+}
+
+const validateData=()=>{
+  //判断品种列是否有值
+  let arr = [];
+  params.tableData.forEach((item) => {
+    item.forEach((item2) => {
+      if (item2.HeadName === "品种") {
+        arr.push(...item2.ValueId);
+      }
+    });
+  });
+  // 判断行是否有值
+  let arr2 = [];
+  params.tableData.forEach((item, index) => {
+    let arr = item.filter((item2) => {
+      if (!item2.Value) return item2;
+    });
+    if (arr.length === params.tableHeadData.length) {
+      arr2.push(index);
+    }
+  });
+
+  // 商品复盘、周报、双周报、数据点评、月报 品种必须有一个选择了
+  let arr3=[]
+  params.tableData.forEach(item=>{
+    item.forEach(item2=>{
+      if(item2.Value==='数据点评'||
+        item2.Value==='FICC周报'||
+        item2.Value==='商品双周报+线上电话会讨论会<br/>(由弘则的研究员主持线上讨论)'){
+          item.forEach(item3=>{
+            if (item3.HeadName === "品种") {
+              arr3.push(...item3.ValueId);
+            }
+          })
+      }
+    })
+  })
+
+  if (!arr.length) {
+    ElMessage.warning("至少选择一个品种");
+    return false;
+  }
+
+  if (arr2.length) {
+    ElMessage.warning("行内至少填一项");
+    return false;
+  }
+
+  if(!arr3.length){
+    ElMessage.warning("周报、双周报、数据点评至少选择一个品种");
+    return false;
+  }
+  // console.log(arr,arr2,arr3);
+  return true;
+}
+
+const submit=()=>{
+  let flag = validateData();
+  if (!flag) return;
+  // console.log(params.tableData);
+  // 生成表格图片
+  html2canvas(document.getElementById("table-png"), {
+    backgroundColor: "#ffffff",
+    useCORS: true, // 允许图片跨域
+    allowTaint: true, // 在渲染前测试图片
+    imageTimeout: 0, // 加载延时
+    scale:3,
+  }).then((res) => {
+    let img = res.toDataURL("image/png");
+    emits("selectFinish", {
+      Value: img,
+      chart_permission_ids:varietyDia.chart_permission_ids||'1',
+      service_template_id: props.service.service_template_id,
+      tableData: params.tableData,
+      tableHeadData: params.tableHeadData,
+    });
+    serviceClose();
+  });
+}
+const closeDia=()=>{
+  serviceClose()
+  initData()
+}
+
+const serviceClose=()=>{
+  params.showRightClickMenu = false;
+  params.selectRowIndex = null;
+  emits('update:visible',false)
+}
+
+
+// ---------------created
+  getPermissionListFun()
+</script>
+
+<template>
+  <div id="service-variety-container">
+    <el-dialog title="选择品种" :model-value="props.visible" top="5vh" 
+    @close="serviceClose" :close-on-click-modal="false" width="900" >
+      <div style="max-height: 66vh; overflow-y: auto; padding: 10px 0">
+        <table id="table" ref="table" class="table-wrap" @click="params.showRightClickMenu = false">
+          <thead>
+            <tr style="background: #f0f2f5">
+              <th class="table-item" v-for="item in params.tableHeadData" :key="item.Value">{{ item.Value }}</th>
+            </tr>
+          </thead>
+
+          <tbody>
+            <tr v-for="(row, rindex) in params.tableData" :key="rindex" :class="rindex === params.selectRowIndex ? 'row-dark' : null">
+              <td 
+                class="table-item" 
+                v-for="(item, cindex) in row" 
+                :key="item" 
+                @click="handleSelectRow(rindex,item)" 
+                @contextmenu.prevent="rightClick(rindex, cindex, $event)" 
+                :data-rindex="rindex" 
+                :data-cindex="cindex"
+              >
+                <div v-if="item.Type === 'text'">
+                  <el-icon v-if="item.HeadName=='品种'&&!item.RowDisable" color="var(--themeColor)"
+                  size="20" style="cursor: pointer;" class="choose-icon" ><EditPen /></el-icon>
+                  <span 
+                    v-if="!item.CanEdit" 
+                    v-html="item.Value" 
+                    :style="{paddingLeft:item.HeadName=='品种'&&!item.RowDisable?'25px':null,display:'inline-block'}"
+                  ></span>
+                  <el-input v-else type="textarea" placeholder="请输入" v-model="item.Value"></el-input>
+                </div>
+                <div v-if="item.Type === 'select'">
+                  <div style="margin-bottom: 10px;font-size:15px">{{ item.Value }}</div>
+                  <div style="text-align: left">
+                    <el-checkbox-group v-model="params.tableSelectCheck[item.Value]">
+                      <el-checkbox style="font-size:14px" :label="opt.id" v-for="opt in params.tableSelectList[item.Value]" :key="opt.id" @change="handleTableSelectChange(item.Value, rindex, cindex)">{{ opt.name }}</el-checkbox>
+                    </el-checkbox-group>
+                  </div>
+                </div>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+      <template #footer>
+        <el-button type="primary" @click="submit" style="color: white;">保存</el-button>
+        <el-button @click="closeDia">取消</el-button>
+      </template> 
+    </el-dialog>
+    <!-- 右键选项弹框 -->
+    <div class="right-click-wrap" :style="params.position" v-show="params.showRightClickMenu">
+      <div @click="handleUpdateTable(item.type)" v-for="item in params.rightClickCon" :key="item.type">{{ item.name }}</div>
+    </div>
+    <!-- 新增列填写表头弹窗 -->
+    <el-dialog v-model="params.showInputHead" width="560px" top="20vh" title="添加列">
+      <el-form :model="params.form" ref="headTitleForm" label-width="100px" @submit.native.prevent>
+        <el-form-item label="表格名称" prop="title" :rules="{ required: true, message: '请填写名称', trigger: 'blur' }">
+          <el-input v-model="params.form.title"></el-input>
+        </el-form-item>
+        <div style="text-align: center; margin-top: 70px; margin-bottom: 30px">
+          <el-button type="primary" @click="handleConfirmHead" style="height: 36px;width: 120px;">保存</el-button>
+          <el-button type="primary" plain @click="handleCancelHead" style="height: 36px;width: 120px;">取消</el-button>
+        </div>
+      </el-form>
+    </el-dialog>
+    <!-- 生成表格图片dom -->
+    <table id="table-png" class="table-wrap">
+      <thead>
+        <tr style="background: #f0f2f5">
+          <th class="table-item" v-for="item in params.tableHeadData" :key="item.Value" style="color: #333;font-size:15px">{{ item.Value }}</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="(row, rindex) in params.tableData" :key="rindex">
+          <td class="table-item" v-for="item in row" :key="item" style="max-width: 300px">
+            <div v-html="item.Value" style="color: #333;font-size:14px"></div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <!-- 选择具体品种弹窗 -->
+    <el-dialog title="选择品种" v-model="varietyDia.dialogShow" :close-on-click-modal="false" width="700" >
+      <div style="padding: 20px 30px;border: dashed 1px #aab4cc;border-radius: 4px;">
+        <div v-for="item in varietyDia.varietyList" :key="item.classify_name" style="display: flex;margin-bottom: 20px;">
+          <el-checkbox v-model="item.checked" :disabled="params.selectRowIndex === null"
+          :indeterminate="item.check_list.length>0 && item.check_list.length < item.items.length" @change="varietyAllChecked(item)">
+          {{item.classify_name}}:</el-checkbox>
+          <el-checkbox-group v-model="item.check_list" @change="varietyCheckedChange(item)" :disabled="params.selectRowIndex === null">
+            <el-checkbox :label="it.chart_permission_id" v-for="it in item.items" :key="it.chart_permission_id"
+            :disabled="it.chart_permission_id=='1'">{{it.permission_name}}</el-checkbox>
+          </el-checkbox-group>
+        </div>
+      </div>
+      <template #footer>
+        <el-button type="primary" @click="varietiesSelect" style="color: white;">保存</el-button>
+        <el-button @click="varietiesCancel">取消</el-button>
+      </template> 
+    </el-dialog>
+  </div>
+    
+</template>
+  
+<style lang="scss" scoped>
+.table-wrap {
+  position: relative;
+  width: 100%;
+  text-align: center;
+  border-top: 1px solid #ebeef5;
+  border-left: 1px solid #ebeef5;
+  .row-dark {
+    background-color:#ebeef5;
+  }
+  .table-item {
+    padding: 20px 10px;
+    border-right: 1px solid #ebeef5;
+    border-bottom: 1px solid #ebeef5;
+    min-width: 160px;
+    position: relative;
+    .table-item-mask {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0, 0, 0, 0);
+      z-index: 1000;
+    }
+    .choose-icon{
+      position: absolute;
+      top: 50%;
+      left: 10px;
+      transform:translateY(-50%);
+    }
+  }
+}
+.right-click-wrap {
+  border-radius: 4px;
+  background-color: #fff;
+  padding: 10px 0;
+  position: fixed;
+  z-index: 5000;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  line-height: 2;
+  div {
+    cursor: pointer;
+    padding: 0 20px;
+    &:hover {
+      background-color: #ebeef5;
+    }
+  }
+}
+#table-png {
+  position: absolute;
+  z-index: -10;
+  top: -100%;
+  left: 0;
+  width: 1000px;
+}
+</style>

+ 1060 - 0
src/views/financialManagement/contractProgress.vue

@@ -0,0 +1,1060 @@
+
+<script setup>
+  import serviceVarietyDia from './components/serviceVarietyDia.vue'
+  import {useRouter,useRoute} from 'vue-router'
+  import {ElMessage} from 'element-plus'
+  import {getSellerList,getContractSearchList} from '@/api/crm'
+  import {getServiceList,registerAdd,registerDetail,registerEdit,registerInvoice,registerPayment} from '@/api/financialMana'
+
+  const router = useRouter()
+  const route = useRoute()
+
+  const contractInfoForm=ref(null)
+  // 开票记录表单
+  const invoiceFormRef=ref(null)
+  // 到款记录表单
+  const placementFormRef=ref(null)
+  // 合同信息
+  const contractInfo=reactive({
+    /*
+      操作类型
+      查看-view 合规登记-compliance 开票登记-invoice 到款登记-placement
+    */ 
+    operationtype:"compliance",
+    form:{
+      product_id:1,
+      contract_register_id:'',
+      crm_contract_id:0,
+      contract_source:0,
+      contract_code:'',
+      company_name:'',
+      seller_id:'',
+      seller_name:'',
+      contract_status:'',
+      start_date:'',
+      end_date:'',
+      contract_amount:'',
+      contract_type:'',
+      sign_date:'',
+      agreed_pay_time:'',
+      services:[],
+      remark:""
+    },
+    moneyData:{
+      haveInvoiceMoney:'',
+      waitInvoiceMoney:'',
+      allInvoiceMoney:'',
+      // 到款金额
+      havePlacementMoney:'',
+      waitPlacementMoney:'',
+      allPlacementMoney:''
+    },
+    // 有效期
+    contractValidityDate:[],
+    //销售列表
+    sellerList:[],
+    rules:{
+      contract_source:{required:true,message:'合同来源不能为空',trigger:'change'},
+      contract_code:{required:true,message:'合同编号不能为空',trigger:'blur'},
+      company_name:{required:true,message:'客户名称不能为空',trigger:'blur'},
+      seller_id:{required:true,message:'销售姓名不能为空',trigger:['blur','change']},
+      contract_status:{required:true,message:'合同状态不能为空',trigger:'change'},
+      start_date:{required:true,message:'合同有效期不能为空',trigger:'change'},
+      contract_amount:[{required:true,message:'合同金额不能为空',trigger:'blur'},
+      {
+        validator:(rule,value,callback)=>{
+          if(!parseFloat(value)){
+            callback(new Error('合同金额格式错误'))
+          }else{
+            callback()
+          }
+        },
+        trigger:'blur'
+      }],
+      contract_type:{required:true,message:'合同类型不能为空',trigger:'change'},
+      sign_date:{required:true,message:'签订日不能为空',trigger:'change'},
+      agreed_pay_time:{required:true,message:'约定付款时间不能为空',trigger:'blur'}
+
+    },
+    // 合同编号----------------------列表
+    contractNoArray:[],
+    // 请求列表参数
+    contractNoQuery:{
+      page_size:20,
+      current:1,
+      product_id:1,
+      keyword:''
+    },
+    contractNoTotal:0,
+    contractNoLoading:false,
+    // --------------套餐信息
+    // 选中的套餐
+    checkedService:[],
+    //套餐列表
+    serviceArray:[],
+    // 当前小套餐选中的品种
+    currentSmallService:{},
+    // checkedServiceDetail:{},
+    // 查看套餐弹窗
+    serviceShow:false,
+    // 显示选择品种 - FICC小套餐
+    serviceVarietyShow:false,
+    // 1-大套餐  2-小套餐
+    serviceType:0,
+    // 品种选择
+    varietyDiaShow:false,
+    // -----------------登记进度
+    progressList:[],
+  })
+  // ---------------开票信息
+  const invoiceForm=reactive({
+    invoiceData:[
+      {
+        amount:'',
+        invoice_date:''
+      }
+    ],
+    rowErrorShow:''
+  })
+  // ---------------到款信息
+  const placementForm=reactive({
+    placementData:[
+      {
+        amount:'',
+        invoice_date:''
+      }
+    ],
+    rowErrorShow:''
+  })
+
+  const previewImage=ref('')
+  const previewImageTitle=ref('')
+  watch(()=> contractInfo.moneyData.haveInvoiceMoney ,(newValue)=>{
+    contractInfo.moneyData.waitInvoiceMoney = contractInfo.moneyData.allInvoiceMoney-newValue
+    contractInfo.moneyData.waitInvoiceMoney=Math.round(contractInfo.moneyData.waitInvoiceMoney*100)/100
+  })
+  watch(()=> contractInfo.moneyData.havePlacementMoney ,(newValue)=>{
+    contractInfo.moneyData.waitPlacementMoney = contractInfo.moneyData.allPlacementMoney-newValue
+    contractInfo.moneyData.waitPlacementMoney=Math.round(contractInfo.moneyData.waitPlacementMoney*100)/100
+  })
+  const contractSourceArray=['非CRM合同导入','CRM合同导入']
+  const contractStatusArray=[{id:1,label:"已审批"},{id:2,label:"单章寄回"},{id:3,label:"已签回"}]
+  const contractTypeArray=[{id:1,label:"新签"},{id:2,label:"续约"}]
+  const operationType=[{op_type:1,label:"合规登记"},{op_type:2,label:"开票登记"},{op_type:3,label:"到款登记"},
+  {op_type:4,label:"修改合同状态"},{op_type:5,label:"删除合同登记"}]
+  // -----------------------method
+  //获取销售列表
+  const getSellerListFun=()=>{
+    getSellerList().then(res=>{
+      contractInfo.sellerList=res.data || []
+      // console.log(contractInfo.sellerList);
+    })
+  }
+  // 获取服务列表
+  const getServiceListFun=()=>{
+    getServiceList().then(res=>{
+      contractInfo.serviceArray=res.data || []
+      // console.log(contractInfo.serviceArray);
+    })
+  }
+
+  // 有效期变更触发函数
+  const contractValidityDateChane=(value)=>{
+    if(value){
+      contractInfo.form.start_date=value[0]
+      contractInfo.form.end_date=value[1]
+    }else{
+      contractInfo.form.start_date=contractInfo.form.end_date=''
+    }
+  }
+  // 合同来源改变
+  const contractSourceChange=(value)=>{
+    // 切换来源,清空数据
+    contractInfo.form={...contractInfo.form,
+      crm_contract_id:0,
+      contract_code:'',
+      company_name:'',
+      seller_id:'',
+      seller_name:'',
+      contract_status:'',
+      start_date:'',
+      end_date:'',
+      contract_amount:'',
+      contract_type:'',
+      sign_date:'',
+      agreed_pay_time:'',
+      remark:""
+    }
+    contractInfo.contractValidityDate=[]
+    setTimeout(()=>{
+      contractInfoForm.value && contractInfoForm.value.clearValidate()
+    },0)
+  }
+  // 服务改变
+  const serciveChange=(value,type,openDia=true)=>{
+    // console.log(value,type);
+    if(type==2){
+      contractInfo.serviceVarietyShow=value
+      contractInfo.varietyDiaShow=openDia&&value
+    }
+    contractInfo.serviceType = value?type:0
+  }
+  const getVarieties=(item)=>{
+    contractInfo.currentSmallService= { 
+      service_template_id: item.service_template_id,
+      Value: item.Value,
+      tableData: item.tableData, 
+      chart_permission_ids:item.chart_permission_ids,
+      tableHeadData: item.tableHeadData 
+    }
+    // console.log(item);
+  }
+  // 合同编号远程搜索
+  const contractNoSearch=(value)=>{
+    if(!value.trim()) return 
+    contractInfo.contractNoQuery.keyword=value
+    contractInfo.contractNoQuery.current=1
+    contractInfo.contractNoArray=[]
+    getContractSearchListFun()
+    // console.log(value);
+  }
+  const loadContractNoMore=()=>{
+    if(contractInfo.contractNoArray.length >=contractInfo.contractNoTotal) return 
+    contractInfo.contractNoQuery.current++
+    getContractSearchListFun()
+  }
+  const getContractSearchListFun=()=>{
+    contractInfo.contractNoLoading=true 
+    getContractSearchList(contractInfo.contractNoQuery).then(res=>{
+      contractInfo.contractNoArray=contractInfo.contractNoArray.concat(res.data.list || [])
+      contractInfo.contractNoTotal=res.data.page.total
+    }).finally(()=>{
+      contractInfo.contractNoLoading=false
+    })
+  }
+  // 合同编号选中
+  const selectContractNo=(value)=>{
+    let selectItem=contractInfo.contractNoArray.find(item=>item.contract_code == value)
+    // console.log(selectItem);
+    contractInfo.form.company_name=selectItem.company_name
+    contractInfo.form.crm_contract_id=selectItem.contract_id
+    contractInfo.form.start_date=selectItem.start_date
+    contractInfo.form.end_date=selectItem.end_date
+    contractInfo.contractValidityDate=[selectItem.start_date,selectItem.end_date]
+    contractInfo.form.contract_amount=selectItem.price
+    contractInfo.form.seller_name=selectItem.seller_name
+    contractInfo.form.seller_id=selectItem.seller_id
+    contractInfo.form.contract_type=selectItem.contract_type_key
+    contractInfoForm.value && contractInfoForm.value.validateField()
+  }
+  // 销售选中
+  const selectSeller=(value)=>{
+    contractInfo.form.seller_name=contractInfo.sellerList.find(item => item.admin_id==value).real_name
+  }
+
+  const submit=()=>{
+    // 查看-view 合规登记-compliance 开票登记-invoice 到款登记-placement
+    if(contractInfo.operationtype=='compliance'){
+      complianceSubmit()
+    }else if(contractInfo.operationtype=='invoice'){
+      invoiceSubmit()
+    }else if(contractInfo.operationtype=='placement'){
+      placementSubmit()
+    }
+  }
+  // 表格添加行
+  const addRow=(type,index)=>{
+    if(type=='invoice'){
+      invoiceForm.invoiceData.splice((index+1),0,{amount:'',invoice_date:''})
+    }else{
+      placementForm.placementData.splice((index+1),0,{amount:'',invoice_date:''})
+    }
+  }
+  // 表格删除行
+  const deleteRow=(type,index)=>{
+    if(type=='invoice'){
+      if(invoiceForm.invoiceData[index].amount=='' && invoiceForm.invoiceData[index].invoice_date==''){
+        // 没有内容 直接删除
+        invoiceForm.invoiceData.splice(index,1)
+      }else{
+        ElMessageBox.confirm('删除后不可恢复,是否删除该条开票记录?',
+        '提示',    
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        }).then(res=>{
+          // 有金额才进行删除后的运算
+          if(invoiceForm.invoiceData[index].amount!=''){
+            invoiceForm.invoiceData.splice(index,1)
+            let money=0
+            invoiceForm.invoiceData.map(item =>{
+              money+=parseFloat(item.amount) || 0
+              // console.log(money);
+            })
+            // 保留两位小数、防止计算时精度丢失
+            money=Math.round(money*100)/100
+            contractInfo.moneyData.haveInvoiceMoney=money
+          }else{
+            invoiceForm.invoiceData.splice(index,1)
+          }
+        }).catch(()=>{})
+      }
+    }else{
+      if(placementForm.placementData[index].amount=='' && placementForm.placementData[index].invoice_date==''){
+        // 没有内容 直接删除
+        placementForm.placementData.splice(index,1)
+      }else{
+        ElMessageBox.confirm('删除后不可恢复,是否删除该条到款记录?',
+        '提示',    
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        }).then(res=>{
+          // 有金额才进行删除后的运算
+          if(placementForm.placementData[index].amount!=''){
+            placementForm.placementData.splice(index,1)
+            let money=0
+            placementForm.placementData.map(item =>{
+              money+=parseFloat(item.amount) || 0
+              // console.log(money);
+            })
+            // 保留两位小数、防止计算时精度丢失
+            money=Math.round(money*100)/100
+            contractInfo.moneyData.havePlacementMoney=money
+          }else{
+            placementForm.placementData.splice(index,1)
+          }
+        }).catch(()=>{})
+      }
+    }
+  }
+  // 金额改变
+  const moneyChange=(type,value,index)=>{ 
+    // console.log(value);
+    let numerValue=parseFloat(value)
+    if(type=='invoice'){
+      if(!numerValue&&numerValue!=0&&value){
+        invoiceForm.invoiceData[index].amount=''
+        ElMessage.error('请输入数字')
+        return
+      }
+      // 如果输入不是 以.结尾&&只有一个.的话,就赋值为转为成数字后的值
+      if(!(value.endsWith('.') && value.indexOf('.')==(value.length-1))&&numerValue){
+        invoiceForm.invoiceData[index].amount=numerValue
+      }
+      // console.log(numerValue);
+
+      let money=0
+      invoiceForm.invoiceData.map(item =>{
+        money+=parseFloat(item.amount) || 0
+      })
+      // 保留两位小数、防止计算时精度丢失
+      money=Math.round(money*100)/100
+      contractInfo.moneyData.haveInvoiceMoney=money
+
+    }else{
+      if(!numerValue&&numerValue!=0&&value){
+        placementForm.placementData[index].amount=''
+        ElMessage.error('请输入数字')
+        return
+      }
+      // 如果输入不是 以.结尾&&只有一个.的话,就赋值为转为成数字后的值
+      if(!(value.endsWith('.') && value.indexOf('.')==(value.length-1))&&numerValue){
+        placementForm.placementData[index].amount=numerValue
+      }
+
+      let money=0
+      placementForm.placementData.map(item =>{
+        money+=parseFloat(item.amount) || 0
+      })
+      // 保留两位小数、防止计算时精度丢失
+      money=Math.round(money*100)/100
+      contractInfo.moneyData.havePlacementMoney=money
+    }
+    
+  }
+  // 金额格式化,保留两位小数
+  const moneyFormatter=(money)=>{
+    let result=money.toString()
+    if(result.indexOf('.')==-1){
+      // 没有小数
+      return result+'.00'
+    }else{
+      let arr = result.split('.')
+      let decimal = arr[1].padEnd(2,'0')
+      return arr[0]+'.'+decimal
+    }
+  }
+  // 合规登记-提交
+  const complianceSubmit=()=>{
+    contractInfoForm.value.validate(valid=>{
+      if(valid){
+        if(contractInfo.checkedService.length==0){
+          ElMessage.warning('请选择套餐')
+          return 
+        }
+        if(!contractInfo.currentSmallService.Value && contractInfo.checkedService.some(serviceId =>serviceId==2)){
+          ElMessage.warning('请保存FICC小套餐品种')
+          return 
+        }
+        contractInfo.form.services=[]
+        contractInfo.checkedService.map(serviceId=>{
+          let serviceItem = contractInfo.serviceArray.find(it=> it.service_template_id==serviceId) || {}
+          // 小套餐
+          if(serviceId==2){
+            contractInfo.form.services.push({
+              service_template_id:contractInfo.currentSmallService.service_template_id,
+              value:contractInfo.currentSmallService.Value,
+              chart_permission_ids:contractInfo.currentSmallService.chart_permission_ids,
+              detail:[contractInfo.currentSmallService.tableHeadData,...contractInfo.currentSmallService.tableData],
+              chart_permission_id:serviceItem.chart_permission_id,
+              title:serviceItem.title
+            })
+          }else{
+            contractInfo.form.services.push(serviceItem)
+          }
+        })
+        contractInfo.form.contract_amount = parseFloat(contractInfo.form.contract_amount)
+        if(contractInfo.form.contract_register_id){
+          // 编辑
+          registerEdit(contractInfo.form).then(res=>{
+            let messageHint=ElMessage.success('合规登记编辑成功')
+            setTimeout(()=>{
+              messageHint.close()
+              router.back()
+            },1000)
+          })
+        }else{
+          // 新增
+          registerAdd(contractInfo.form).then(res=>{
+            let messageHint=ElMessage.success('合规登记成功')
+            setTimeout(()=>{
+              messageHint.close()
+              router.back()
+            },1000)
+          })
+        }
+      }
+    })
+  }
+
+  // 开票登记保存
+  const invoiceSubmit=()=>{
+    invoiceFormRef.value.validate(valid=>{
+      if(valid){
+        invoiceForm.rowErrorShow=''
+        if(contractInfo.moneyData.waitInvoiceMoney<0){
+          ElMessage.error('总开票金额大于合同金额,请检查')
+          // 让金额这一列标红
+          invoiceForm.rowErrorShow='true'
+          return
+        }
+        // 转化
+        invoiceForm.invoiceData.forEach(element => {
+          element.amount = parseFloat(element.amount)
+        });
+        let param={
+          contract_register_id:contractInfo.form.contract_register_id,
+          invoice_type:1,
+          amount_list:invoiceForm.invoiceData
+        }
+        registerInvoice(param).then(res=>{
+          let messageHint=ElMessage.success('开票登记成功')
+          setTimeout(()=>{
+            messageHint.close()
+            router.back()
+          },1000)
+        })
+      }
+    })
+  }
+  // 到款登记保存
+  const placementSubmit=()=>{
+    placementForm.rowErrorShow=''
+    placementFormRef.value.validate(valid=>{
+      if(valid){
+        if(contractInfo.moneyData.waitPlacementMoney<0){
+          ElMessage.error('总开票金额大于合同金额,请检查')
+          // 让金额这一列标红
+          placementForm.rowErrorShow='true'
+          return
+        }
+        // 转化
+        placementForm.placementData.forEach(element => {
+          element.amount = parseFloat(element.amount)
+        });
+        let param={
+          contract_register_id:contractInfo.form.contract_register_id,
+          invoice_type:2,
+          amount_list:placementForm.placementData
+        }
+        registerPayment(param).then(res=>{
+          let messageHint=ElMessage.success('到款登记成功')
+          setTimeout(()=>{
+            messageHint.close()
+            router.back()
+          },1000)
+        })
+      }
+    })
+  }
+  // 取消操作
+  const registrationCancel=()=>{
+    router.back()
+  }
+  const canServiceShow=(serviceTemplateId)=>{
+    return contractInfo.form.services.find(item => item.service_template_id == serviceTemplateId) && contractInfo.operationtype!='compliance'
+  }
+  // 查看套餐
+  const viewService=(serviceTemplateId)=>{
+    if(!canServiceShow(serviceTemplateId)) return 
+    let viewItem=contractInfo.form.services.find(item => item.service_template_id == serviceTemplateId)
+    previewImageTitle.value = viewItem.title
+    previewImage.value=viewItem.value
+    contractInfo.serviceShow=true
+  }
+
+// ----------------------created
+  getSellerListFun()
+  getServiceListFun()
+
+  contractInfo.form.contract_register_id = parseInt(route.query.complianceId) || ''
+  // id没有,认为是合规登记
+  contractInfo.operationtype=contractInfo.form.contract_register_id?(route.query.type || 'compliance'):'compliance'
+  if(contractInfo.form.contract_register_id){
+    //请求详情接口
+    registerDetail({contract_register_id:contractInfo.form.contract_register_id}).then(res=>{
+      contractInfo.progressList=res.data.logs || [{}]
+      contractInfo.form={...contractInfo.form,
+        crm_contract_id:res.data.crm_contract_id,
+        contract_code:res.data.contract_code,
+        company_name:res.data.company_name,
+        seller_id:res.data.seller_id,
+        seller_name:res.data.seller_name,
+        contract_status:res.data.contract_status,
+        start_date:res.data.start_date,
+        end_date:res.data.end_date,
+        contract_amount:res.data.contract_amount,
+        contract_type:res.data.contract_type,
+        sign_date:res.data.sign_date,
+        agreed_pay_time:res.data.agreed_pay_time,
+        remark:res.data.remark,
+        contract_source:res.data.contract_source,
+        services:res.data.service_list
+      }
+      contractInfo.contractValidityDate=[res.data.start_date,res.data.end_date]
+      contractInfo.checkedService=res.data.service_list.map(item => item.service_template_id)
+      contractInfo.checkedService.map(serviceId=>{
+        // 大套餐或者小套餐
+        if(serviceId==1 || serviceId==2)
+        serciveChange(true,serviceId,false)
+      })
+      // 开票
+      contractInfo.moneyData.allInvoiceMoney = contractInfo.moneyData.allPlacementMoney=res.data.contract_amount
+      contractInfo.moneyData.haveInvoiceMoney=res.data.invoiced_amount
+      contractInfo.moneyData.havePlacementMoney=res.data.payment_amount
+      if(res.data.invoice_list.length>0){
+        invoiceForm.invoiceData=[]
+        res.data.invoice_list.map(item=>{
+          invoiceForm.invoiceData.push({
+            invoice_id:item.invoice_id,
+            amount:item.amount,
+            invoice_date:item.invoice_time
+          })
+        })
+      }
+      if(res.data.payment_list.length>0){
+        placementForm.placementData=[]
+        res.data.payment_list.map(item=>{
+          placementForm.placementData.push({
+            invoice_id:item.invoice_id,
+            amount:item.amount,
+            invoice_date:item.invoice_time
+          })
+        })
+      }
+      let samllService=contractInfo.form.services.find(item => item.service_template_id==2)
+      if(contractInfo.operationtype=='compliance' && samllService){
+        // 合规编辑
+        /**
+         * 有可能服务模板的接口数据还没返回
+         * 这时更改contractInfo.currentSmallService会触发子组件的监听函数,导致空指针错误
+         * 创建一个 setInterval 询问contractInfo.serviceArray数据是否返回,已返回就更新contractInfo.currentSmallService
+         */
+         let temarr = samllService.detail.map((rowItem) => {
+          let rowArr = [];
+          for (let key in rowItem) {
+              if (key.substr(0, 3) === "col" && rowItem[key] !== "") {
+                rowArr.push(JSON.parse(rowItem[key]));
+              }
+            }
+            return rowArr;
+          });
+        let paramsTemp={
+          tableHeadData:temarr[0],
+          tableData:temarr.slice(1),
+          service_template_id:samllService.service_template_id,
+          Value:samllService.value,
+          chart_permission_ids:samllService.chart_permission_ids
+        }
+          paramsTemp.tableHeadData = temarr[0];
+          paramsTemp.tableData = temarr.slice(1);
+        if(contractInfo.serviceArray.length==0){
+          let timer=setInterval(()=>{
+            if(contractInfo.serviceArray.length>0){
+              contractInfo.currentSmallService=paramsTemp
+              clearInterval(timer)
+            }
+          },200)
+        }else{
+          contractInfo.currentSmallService=paramsTemp
+        }
+      }
+    })
+  }
+</script>
+
+<template>
+    <div class="contract-progress-container" id="contract-progress-container">
+      <div class="contract-progress">
+        <div class="contract-progress-main" >
+          <!-- 合规登记 -->
+          <div class="info-box" >
+            <div class="info-box-head">合规登记</div>
+            <!-- 合同信息 -->
+            <div class="info-row" >
+              <div class="info-row-title">合同信息</div>
+              <div style="margin: 0 74px 0 63px;">
+                <el-form :model="contractInfo.form" inline ref="contractInfoForm" label-width="130" :disabled="contractInfo.operationtype!='compliance'"
+                :rules="contractInfo.rules" style="display: flex;flex-wrap: wrap;justify-content: space-around;">
+                  <el-form-item label="合同来源" prop="contract_source">
+                    <el-select v-model="contractInfo.form.contract_source" placeholder="请选择合同来源" @change="contractSourceChange">
+                      <el-option :label="item" :value="index" 
+                      v-for="(item,index) in contractSourceArray" :key="item"></el-option>
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="合同编号" prop="contract_code" id="selectContract">
+                    <el-input v-model="contractInfo.form.contract_code" placeholder="请输入合同编号"
+                    v-if="(contractInfo.form.contract_source==0)"/>
+                    <el-select :teleported="false" v-optionsLoadMore="loadContractNoMore" v-model="contractInfo.form.contract_code" placeholder="请搜索合同编号" v-else
+                    filterable remote :remote-method="contractNoSearch" :loading="false" @change="selectContractNo">
+                      <el-option :label="item.contract_code" :value="item.contract_code" v-for="item in contractInfo.contractNoArray" :key="item.contract_id"></el-option>
+                      <div style="height: 40px;width:100%;position: absolute;bottom: 0;pointer-events: none;" v-loading="contractInfo.contractNoLoading"></div>
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="客户名称" prop="company_name">
+                    <el-input v-model="contractInfo.form.company_name"
+                    placeholder="请输入客户名称"  />
+                  </el-form-item>
+                  <el-form-item label="销售" prop="seller_id">
+                    <el-select v-model="contractInfo.form.seller_id" placeholder="请选择销售" filterable @change="selectSeller">
+                      <el-option :label="item.real_name" :value="item.admin_id" v-for="item in contractInfo.sellerList" :key="item.admin_id"></el-option>
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="合同状态" prop="contract_status">
+                    <el-select v-model="contractInfo.form.contract_status" 
+                     placeholder="请选择合同状态" >
+                      <el-option :label="item.label" :value="item.id" v-for="item in contractStatusArray" :key="item.id"></el-option>
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="合同有效期" 
+                  prop="start_date">
+                    <el-date-picker type="daterange" 
+                    v-model="contractInfo.contractValidityDate" @change="contractValidityDateChane"
+                    start-placeholder="合同有效期-开始" range-separator="至" end-placeholder="合同有效期-结束" value-format="YYYY-MM-DD"
+                    :clearable="false">
+                    </el-date-picker>
+                  </el-form-item>
+                  <el-form-item label="合同金额"
+                  prop="contract_amount">
+                    <el-input v-model.trim="contractInfo.form.contract_amount"
+                    placeholder="请输入合同金额" />
+                  </el-form-item>
+                  <el-form-item label="合同类型" prop="contract_type">
+                    <el-select v-model="contractInfo.form.contract_type"
+                    placeholder="请选择合同类型" >
+                      <el-option :label="item.label" :value="item.id" v-for="item in contractTypeArray" :key="item.id"></el-option>
+                    </el-select>
+                  </el-form-item>
+                  <el-form-item label="签订日" prop="sign_date">
+                    <el-date-picker v-model="contractInfo.form.sign_date"
+                    placeholder="请选择签订日" value-format="YYYY-MM-DD"
+                    :clearable="false">
+                    </el-date-picker>
+                  </el-form-item>
+                  <el-form-item label="约定付款时间" prop="agreed_pay_time">
+                    <el-input v-model="contractInfo.form.agreed_pay_time"
+                    placeholder="请输入约定付款时间" />
+                  </el-form-item>
+                </el-form>
+              </div>
+            </div>
+            <div class="info-row">
+              <div class="info-row-title">套餐信息</div>
+              <el-checkbox-group :disabled="contractInfo.operationtype!='compliance'"
+              v-model="contractInfo.checkedService" class="info-service-box">
+                <el-checkbox :label="contractInfo.serviceArray[0]?.service_template_id" @change="(e) => serciveChange(e,1)" 
+                  :class="{'viewService':canServiceShow(contractInfo.serviceArray[0]?.service_template_id)}"
+                :disabled="contractInfo.serviceType==2" style="margin-right: 0;" @click="viewService(contractInfo.serviceArray[0]?.service_template_id)">
+                  {{contractInfo.serviceArray[0]?.title}}
+                </el-checkbox>
+                <div class="service-small">
+                  <el-checkbox :label="contractInfo.serviceArray[1]?.service_template_id" @change="(e) => serciveChange(e,2)" 
+                    :class="{'viewService':canServiceShow(contractInfo.serviceArray[1]?.service_template_id)}"
+                    :disabled="contractInfo.serviceType==1" @click="viewService(contractInfo.serviceArray[1]?.service_template_id)">
+                    {{contractInfo.serviceArray[1]?.title}}
+                  </el-checkbox>
+                  <span v-if="(contractInfo.serviceVarietyShow&&contractInfo.operationtype=='compliance')" 
+                  @click="contractInfo.varietyDiaShow=true">选择品种</span>
+                </div>
+                <el-checkbox :label="contractInfo.serviceArray[2]?.service_template_id" style="margin-right: 0;" 
+                @click="viewService(contractInfo.serviceArray[2]?.service_template_id)"
+                 :class="{'viewService':canServiceShow(contractInfo.serviceArray[2]?.service_template_id)}">
+                 {{contractInfo.serviceArray[2]?.title}}
+                </el-checkbox>
+                <el-checkbox :label="contractInfo.serviceArray[3]?.service_template_id">
+                  {{contractInfo.serviceArray[3]?.title}}
+                </el-checkbox>
+              </el-checkbox-group>
+            </div>
+            <div class="info-row">
+                <div class="info-row-title">备注</div>
+                <div class="info-row-remark">
+                  <span style="white-space: nowrap;font-size: 14px;margin-right: 20px;">备注</span>
+                  <el-input style="flex-grow: 1;" :disabled="contractInfo.operationtype!='compliance'"
+                    v-model="contractInfo.form.remark"
+                    placeholder="请输入备注"
+                  />
+                </div>
+            </div>
+            <div class="contract-operation" v-if="contractInfo.operationtype=='compliance'" >
+              <el-button class="operation-button" style="margin-right: 30px;" @click="registrationCancel">取消</el-button>
+              <el-button type="primary" @click="submit" class="operation-button">保存</el-button>
+            </div>
+          </div>
+          <!-- 开票登记 -->
+          <div class="info-box" v-if="contractInfo.operationtype!='compliance'" style="margin-top:20px ;" id="info-invoice-box">
+            <div class="info-box-head">开票登记(待开票)</div>
+            <div class="info-row" >
+              <div class="info-row-title">开票信息</div>
+              <div class="info-row-invoice-payment">
+                <div class="invoice-payment-title">
+                  <div style="margin-right: 30px;">已开票金额(元):<span class="invoice-payment-money">{{moneyFormatter(contractInfo.moneyData?.haveInvoiceMoney)}}</span></div>
+                  <div>剩余开票金额(元):<span class="invoice-payment-money">{{moneyFormatter(contractInfo.moneyData?.waitInvoiceMoney)}}</span></div>
+                </div>
+                <el-form ref="invoiceFormRef" :model="invoiceForm" :disabled="contractInfo.operationtype!='invoice'">
+                  <el-table :data="invoiceForm.invoiceData" border> 
+                    <el-table-column label="序号" width="80" align="center">
+                      <template #default="{row,$index}">
+                        {{$index+1}}
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="开票金额" width="180" align="center" prop="amount">
+                      <template #header>
+                        <span style="color: #FF3400;">*</span>开票金额
+                      </template>
+                      <template #default="{row,$index}">
+                        <el-form-item :prop="`invoiceData.${$index}.amount`" :show-message="false" 
+                        :error="invoiceForm.rowErrorShow"
+                        :rules="{required:true,message:()=>{ ElMessage.error('开票金额不能为空')},trigger:'blur'}">
+                          <el-input v-model.trim="row.amount" style="width: 124px;" 
+                          placeholder="请输入金额" @input="(e)=>moneyChange('invoice',e,$index)"></el-input>
+                        </el-form-item>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="开票日期" width="180" align="center" prop="invoice_date">
+                      <template #header>
+                        <span style="color: #FF3400;">*</span>开票日期
+                      </template>
+                      <template #default="{row,$index}">
+                        <el-form-item :prop="`invoiceData.${$index}.invoice_date`" :show-message="false"
+                        :rules="{required:true,message:()=>{ ElMessage.error('请选择开票日期')},trigger:'change'}">             
+                          <el-date-picker v-model="row.invoice_date" style="width: 124px;"
+                          placeholder="请选择日期" value-format="YYYY-MM-DD" :clearable="false" ></el-date-picker>
+                        </el-form-item>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="操作" width="180" align="center" v-if="contractInfo.operationtype=='invoice'">
+                      <template #default="{row,$index}" >
+                        <span class="table-operation-button" v-if="(invoiceForm.invoiceData.length>1)"
+                        style="color: #FF3400;margin-right: 16px;" @click="deleteRow('invoice',$index)">删除</span>
+                        <span class="table-operation-button" @click="addRow('invoice',$index)">添加</span>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </el-form>
+              </div>
+            </div>
+          </div>
+          <!-- 到款登记 -->
+          <div class="info-box" v-if="contractInfo.operationtype!='compliance'" style="margin-top:20px ;" id="info-invoice-box">
+            <div class="info-box-head">到款登记(待到款)</div>
+            <div class="info-row" >
+              <div class="info-row-title">到款信息</div>
+              <div class="info-row-invoice-payment">
+                <div class="invoice-payment-title">
+                  <div style="margin-right: 30px;">已到款金额(元):<span class="invoice-payment-money">{{moneyFormatter(contractInfo.moneyData.havePlacementMoney)}}</span></div>
+                  <div>剩余到款金额(元):<span class="invoice-payment-money">{{moneyFormatter(contractInfo.moneyData.waitPlacementMoney)}}</span></div>
+                </div>
+                <el-form ref="placementFormRef" :model="placementForm" :disabled="contractInfo.operationtype!='placement'">
+                  <el-table :data="placementForm.placementData" border> 
+                    <el-table-column label="序号" width="80" align="center">
+                      <template #default="{row,$index}">
+                        {{$index+1}}
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="到款金额" width="180" align="center" prop="amount">
+                      <template #header>
+                        <span style="color: #FF3400;">*</span>到款金额
+                      </template>
+                      <template #default="{row,$index}">
+                        <el-form-item :prop="`placementData.${$index}.amount`" :show-message="false" 
+                        :error="placementForm.rowErrorShow"
+                        :rules="{required:true,message:()=>{ ElMessage.error('到款金额不能为空')},trigger:'blur'}">
+                          <el-input v-model="row.amount" style="width: 124px;" 
+                          placeholder="请输入金额" @input="(e)=>moneyChange('placement',e,$index)"></el-input>
+                        </el-form-item>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="到款月" width="180" align="center" prop="invoice_date">
+                      <template #header>
+                        <span style="color: #FF3400;">*</span>到款月
+                      </template>
+                      <template #default="{row,$index}">
+                        <el-form-item :prop="`placementData.${$index}.invoice_date`" :show-message="false"
+                        :rules="{required:true,message:()=>{ ElMessage.error('请选择到款日期')},trigger:'change'}">             
+                          <el-date-picker v-model="row.invoice_date" style="width: 124px;"
+                          placeholder="请选择月份" value-format="YYYY-MM-DD" format="YYYY-MM"></el-date-picker>
+                        </el-form-item>
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="操作" width="180" align="center" v-if="contractInfo.operationtype=='placement'">
+                      <template #default="{row,$index}">
+                        <span class="table-operation-button" @click="deleteRow('placement',$index)"
+                        v-if="(placementForm.placementData.length>1)" style="color: #FF3400;margin-right: 16px;">删除</span>
+                        <span class="table-operation-button" @click="addRow('placement',$index)">添加</span>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </el-form>
+              </div>
+            </div>
+            <div class="contract-operation" v-if="contractInfo.operationtype=='placement' || contractInfo.operationtype=='invoice'">
+              <el-button class="operation-button" style="margin-right: 30px;" @click="registrationCancel">取消</el-button>
+              <el-button type="primary" @click="submit" class="operation-button">保存</el-button>
+            </div>
+          </div>
+        </div>
+        <div class="contract-progress-aside">
+          <p class="progress-aside-title">登记流程</p>
+          <div class="progress-box">
+            <el-timeline>
+              <el-timeline-item color="var(--themeColor)" v-for="item in contractInfo.progressList" 
+              :key="item.name" size="large" placement="top" hide-timestamp>
+                <template #dot>
+                  <div class="customize-circle-outside">
+                    <div class="customize-circle-inside"></div>
+                  </div>
+                </template>
+                <div class="progress-item-title">{{operationType[item.op_type-1]?.label}}</div>
+                <div class="progress-item-info">{{item.admin_name}}</div>
+                <div class="progress-item-info">{{item.create_time}}</div>
+                <div class="progress-item-info" v-show="item.remark">备注:{{item.remark}}</div>
+              </el-timeline-item>
+            </el-timeline>
+          </div>
+        </div>
+      </div>
+      <!-- 小套餐选择品种弹窗 -->
+      <service-variety-dia v-model:visible="contractInfo.varietyDiaShow" @selectFinish="getVarieties" 
+      :service="contractInfo.serviceArray.find(item=>item.service_template_id==2)" :currentService="contractInfo.currentSmallService" ></service-variety-dia>
+      <!-- 查看套餐弹窗 -->
+      <el-dialog v-model="contractInfo.serviceShow" style="min-width: 800px;" title="" width="70vw" top="5vh">
+        <template #header>
+          <div style="text-align: center;">{{previewImageTitle}}</div>
+        </template>
+        <img style="width: 100%; display: block; margin: 0 auto 20px auto" 
+        :src="previewImage" />
+        <div style="text-align: center; margin: 30px 0 10px">
+          <el-button type="primary" @click="contractInfo.serviceShow=false">知道了</el-button>
+        </div>
+      </el-dialog>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .contract-progress-container{
+    .contract-progress{
+      display: flex;
+      .contract-progress-main{
+        flex: 2;
+        margin-right: 20px;
+        .info-box{
+          background-color: white;
+          padding: 0 30px 30px 0;
+          border: 1px solid #E8E8E8;
+          .info-box-head{
+            background-color: $themeColor;
+            font-size: 14px;
+            color: white;
+            height: 40px;
+            display: inline-block;
+            padding: 9px 32px;
+            box-sizing: border-box;
+          }
+          .info-row{
+            margin-top: 48px;
+            .info-row-title{
+              font-weight: 500;
+              font-size: 14px;
+              color: #333333;
+              font-weight: bold;
+              padding-left: 16px;
+              position: relative;
+              margin-bottom: 24px;
+              &::after{
+                content: '';
+                height: 22px;
+                width: 4px;
+                background-color: $themeColor;
+                position: absolute;
+                top: -1.5px;
+                left: 0;
+              }
+            }
+            // 套餐信息
+            .info-service-box{
+              min-width: 560px;
+              width: 560px;
+              padding-left: 80px;
+              margin-top: 20px;
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              .service-small{
+                display: inline-flex;
+                align-items: center;
+                span{
+                  font-size: 14px;
+                  cursor: pointer;
+                  color: $themeColor;
+                  margin-left: 12px;
+                  line-height: 14px;
+                }
+              }
+            }
+            // 备注
+            .info-row-remark{
+              display: flex;
+              padding-left: 80px;
+              align-items: center;
+            }
+            // 开票欣喜
+            .info-row-invoice-payment{
+              padding-left: 80px;
+              display: inline-block;
+              .invoice-payment-title{
+                font-size: 14px;
+                color: #333333;
+                display: flex;
+                align-items: center;
+                margin-bottom: 20px;
+                .invoice-payment-money{
+                  font-weight: 600;
+                  font-size: 14px;
+                  color: #FF3400;
+                }
+              }
+              .table-operation-button{
+                cursor: pointer;
+                color: $themeColor;
+              }
+            }
+          }
+        }
+      }
+      .contract-progress-aside{
+        background-color: white;
+        flex: 1;
+        min-width: 300px;
+        .progress-aside-title{
+          font-size: 18px;
+          font-weight: bold;
+          margin: 30px;
+        }
+        .progress-box{
+          padding:0 20px 20px 70px;
+          .customize-circle-outside{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            background-color: rgba($color: $themeColor, $alpha: 0.4);
+            width: 14px;
+            left: -2px;
+            top: -4px;
+            height: 14px;
+            border-radius: 50%;
+            position: absolute;
+            .customize-circle-inside{
+              background-color:$themeColor;
+              width: 6px;
+              height: 6px;
+              border-radius: 50%;
+            }
+          }
+          .progress-item-title{
+            font-weight: bold;
+            font-size: 16px;
+            color: #333;
+            margin-bottom: 8px;
+          }
+          .progress-item-info{
+            font-size: 14px;
+            color:#666;
+            margin-bottom: 8px;
+            &:last-child{
+              margin-bottom: 0;
+            }
+          }
+        }
+      }
+    }
+    .contract-operation{
+      text-align: center;
+      padding: 100px 30px 30px;
+      .operation-button{
+        height: 40px;
+        width: 130px;
+      }
+    }
+  }
+</style>
+<style lang="scss">
+  #contract-progress-container{
+    .el-timeline-item__tail{
+      border-left:2px solid #D6DBE2 ;
+    }
+    .el-input__wrapper,.el-input{
+      width: 286px;
+    }
+    #info-invoice-box{
+      .el-input__wrapper{
+        width: 124px;
+      }
+      .el-form-item{
+        margin-bottom: 0;
+        .el-form-item__content{
+          justify-content: center;
+        }
+      }
+    }
+    .viewService{
+      .el-checkbox__label{
+        cursor: pointer;
+        color: $themeColor;
+      }
+    }
+  }
+</style>

+ 414 - 0
src/views/financialManagement/financialList.vue

@@ -0,0 +1,414 @@
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import {useRouter,useRoute} from 'vue-router'
+import {getServiceList,getRegisterList,updateRegisterStatus,
+  registerDelete,registerListExport} from '@/api/financialMana'
+
+const router = useRouter()
+const route = useRoute()
+  const changeStatusForm=ref(null)
+
+  const financial=reactive({
+    searchParams:{
+      keyword:'',
+      start_date:'',
+      end_date:'',
+      service_type:'',
+      register_status:'',
+      contract_type:'',
+      page_size:10,
+      current:1,
+    },
+    // updateTime:'',
+    tableData:[],
+    serviceTypeArray:[],
+    total:100,
+    // 是否显示表格列筛选
+    showColumnCheck:false,
+    tabelColumnCheckedArr:[],
+    // 筛选确认数组
+    tabelColumnShowArr:[],
+  })
+  // -----------------弹窗
+  const dialog=reactive({
+    // 开票详情
+    invoiceDetailShow:false,
+    invoiceDetailList:[],
+    // 退款详情
+    refundDetailShow:false,
+    refundDetailList:[],
+    // 更改合同状态
+    changeContractStatusShow:false,
+    contractStatusForm:{
+      contract_register_id:'',
+      contract_status:''
+    },
+    currentStatusRow:{}
+  })
+  const contractTypeArray=[{id:1,label:"新签"},{id:2,label:"续约"}]
+  const contractStatusArray=[{id:1,label:"已审批"},{id:2,label:"单章寄回"},{id:3,label:"已签回"}]
+  const statusArray=[{id:1,label:"进行中"},{id:2,label:"已完成"}]
+
+  // 监听
+  watch(()=>financial.updateTime,(newVal)=>{
+    if(!newVal){
+      financial.searchParams.start_date=''
+      financial.searchParams.end_date=''
+    }else{
+      financial.searchParams.start_date = newVal[0]
+      financial.searchParams.end_date = newVal[1]
+    }
+    searchFinancial()
+  })
+
+//  --------------------------method
+  //获取套餐列表
+  const getServiceListFun=()=>{
+    getServiceList().then(res=>{
+      financial.serviceTypeArray=res.data || []
+    })
+  }
+
+  const financialList=()=>{
+    // console.log(financial.searchParams);
+    getRegisterList(financial.searchParams).then(res=>{
+      financial.tableData = res.data.list || []
+      financial.total = res.data.page.total
+    })
+  }
+  // 切换每页的数量
+  const changePageSize=(pageSize)=>{
+    financial.searchParams.page_size = pageSize
+    financialList()
+  }
+  const changePageNo = (pageNo)=>{
+    financial.searchParams.current = pageNo
+    financialList()
+  }
+  const searchFinancial=()=>{
+    financial.searchParams.current = 1
+    financialList()
+  }
+  // 筛选出现操作
+  const initColumnCheckedArr=()=>{
+    financial.tabelColumnCheckedArr = financial.tabelColumnShowArr
+  }
+  // 确认筛选操作
+  const checkedConfirm=()=>{
+    financial.tabelColumnShowArr = financial.tabelColumnCheckedArr
+    financial.showColumnCheck=false
+  }
+
+  // 合规、开票、到账登记
+  const registration=(type,id)=>{
+    // console.log(type,id);
+  
+    router.push({path:'/financial/list/contractProgress',query:{type,complianceId:id}})
+  }
+  // 导出数据
+  const exportData=()=>{
+    registerListExport(financial.searchParams).then(res=>{
+    // switch (mime) { // 获取后缀对应的 mime
+    //   case 'png': fileTypeMime = 'image/png'; break;
+    //   case 'doc': fileTypeMime = 'application/msword'; break;
+    //   case 'docx': fileTypeMime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; break;
+    //   case 'jpg': case 'jpeg': fileTypeMime = 'image/jpeg'; break;
+    //   case 'gif': fileTypeMime = 'image/gif'; break;
+    //   case 'svg': fileTypeMime = 'image/svg+xml'; break;
+    //   case 'tif': case 'tiff': fileTypeMime = 'image/tiff'; break;
+    //   case 'txt': fileTypeMime = 'text/plain'; break;
+    //   case 'ppt': fileTypeMime = 'application/vnd.ms-powerpoint'; break;
+    //   case 'pptx': fileTypeMime = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; break;
+    //   case 'xls': fileTypeMime = 'application/vnd.ms-excel'; break;
+    //   case 'xlsx': fileTypeMime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; break;
+    //   case 'zip': fileTypeMime = 'application/zip'; break;
+    //   case '7z': fileTypeMime = 'application/x-7z-compressed'; break;
+    // }
+    let blob = window.URL.createObjectURL(new Blob([res], {
+      'type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+    }))
+    let link = document.createElement('a')
+    link.style.display = 'none'
+    link.href = blob
+    link.setAttribute('download', `财务列表.xlsx`)
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link) //下载完成移除元素
+    window.URL.revokeObjectURL(blob) //释放掉 blob 对象
+    })
+  }
+  // 显示开票详情
+  const invoiceDetail=(row)=>{
+    // console.log(row);
+    dialog.invoiceDetailList = row.invoice_list || []
+    dialog.invoiceDetailShow=true
+  }
+  // 到款开票详情
+  const refundDetail=(row)=>{
+    // console.log(row);
+    dialog.refundDetailList = row.payment_list || []
+    dialog.refundDetailShow=true
+  }
+  // 更改合同状态
+  const changeContractStatus=(row)=>{
+    dialog.currentStatusRow = row
+    changeStatusForm.value && changeStatusForm.value.clearValidate()
+    dialog.contractStatusForm.contract_status=row.contract_status
+    dialog.contractStatusForm.contract_register_id=row.contract_register_id
+    dialog.changeContractStatusShow=true
+  }
+  // 提交
+  const submitForm=()=>{
+    if(dialog.contractStatusForm.contract_status == dialog.currentStatusRow.contract_status){
+      dialog.changeContractStatusShow=false
+      return 
+    }
+    changeStatusForm.value.validate((valid)=>{
+      if(valid){
+        // console.log(dialog.contractStatusForm);
+        updateRegisterStatus(dialog.contractStatusForm).then(res=>{
+          financialList()
+          dialog.changeContractStatusShow=false
+          ElMessage.success('更改合同状态成功')
+        })
+      }
+    })
+  }
+  // 删除
+  const delteRecord=(row)=>{
+    ElMessageBox.confirm("删除后不可恢复,是否确认删除该条合规信息?",'操作提示',    
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }).then(res=>{
+      registerDelete({contract_register_id:row.contract_register_id}).then(res=>{
+        ElMessage.success('删除合规登记成功')
+        financialList()
+      })
+    }).catch(()=>{})
+  }
+  //  ---------------------created
+  getServiceListFun()
+  financialList()
+</script>
+
+<template>
+    <div>
+      <router-view></router-view>
+      <template v-if="route.path=='/financial/list'">
+        <div class="financial-list-container" id="financial-list-container" > 
+          <!-- 搜索区域 -->
+          <div class="financial-search-zone">
+            <el-input v-model="financial.searchParams.keyword" placeholder="合同编号/客户姓名/销售" :prefix-icon="Search"
+            style="width: 286px;margin-bottom: 8px;" @input="searchFinancial" clearable />
+            <el-date-picker v-model="financial.updateTime" start-placeholder="合同创建日期"
+            end-placeholder="合同创建日期" style="margin-right: 30px;max-width: 286px;margin-bottom: 8px;"
+            value-format="YYYY-MM-DD" type="daterange"></el-date-picker>
+            <el-select v-model="financial.searchParams.service_type" placeholder="请选择套餐类型" clearable
+            @change="searchFinancial" style="width: 286px;margin-bottom: 8px;">
+              <el-option v-for="item in financial.serviceTypeArray" :key="item.service_template_id"
+              :label="item.title" :value="item.service_template_id"></el-option>
+            </el-select>
+            <el-select v-model="financial.searchParams.contract_type" placeholder="请选择合同类型" clearable
+            @change="searchFinancial" style="width: 286px;margin-bottom: 8px;">
+              <el-option :label="item.label" :value="item.id" v-for="item in contractTypeArray" :key="item.id"></el-option>
+            </el-select>
+            <el-select v-model="financial.searchParams.register_status" placeholder="请选择登记状态" clearable
+            @change="searchFinancial" style="width: 286px;margin-bottom: 8px;">
+              <el-option :label="item.label" :value="item.id" v-for="item in statusArray" :key="item.id"></el-option>
+            </el-select>
+          </div>
+          <!-- 顶部按钮区域 -->
+          <div class="financial-top-option-zone">
+              <el-button type="primary" size="large" style="width: 130px;" v-permission="'financial:list:complianceAdd'"
+              @click="registration('compliance')">合规登记</el-button>
+              <el-button @click="exportData" size="large" style="width: 130px;margin-left: 30px;" >导出</el-button>
+          </div>
+          <div class="financial-table-zone">
+            <!-- 表格 -->
+            <el-table :data="financial.tableData" border max-height="640px" size="default" style="position: sticky;"> 
+              <el-table-column label="合同编号" align="center" show-overflow-tooltip 
+              prop="contract_code" fixed="left" min-width="150"></el-table-column>
+              <el-table-column label="客户名称" align="center" prop="company_name"
+              show-overflow-tooltip min-width="120"></el-table-column>
+              <el-table-column label="销售" align="center" prop="seller_name" width="120"></el-table-column>
+              <el-table-column label="合同类型" align="center" width="90" prop="contract_type"
+              v-if="financial.tabelColumnShowArr.includes('contract_type')">
+                <template #default="{row}">
+                  {{contractTypeArray[row.contract_type-1].label}}
+                </template>
+              </el-table-column>
+              <el-table-column label="套餐信息" align="center" prop="services" min-width="120"></el-table-column>
+              <el-table-column label="合同有效期" align="center" prop="contractDate" width="210" 
+              v-if="financial.tabelColumnShowArr.includes('contractDate')">
+                <template #default="{row}">
+                  {{(row.start_date+' 至 '+row.end_date)}}
+                </template>
+              </el-table-column>
+              <el-table-column label="合同金额" align="center" width="100" prop="contract_amount"></el-table-column>
+              <el-table-column label="约定付款时间" align="center" width="120" prop="agreed_pay_time"
+              v-if="financial.tabelColumnShowArr.includes('agreed_pay_time')"></el-table-column>
+              <el-table-column label="签订日" align="center" prop="sign_date" width="110"
+              v-if="financial.tabelColumnShowArr.includes('sign_date')"></el-table-column>
+              <el-table-column label="合同状态" align="center" width="90" prop="contract_status">
+                <template #default="{row}">
+                  {{contractStatusArray[row.contract_status-1].label}}
+                </template>
+              </el-table-column>
+              <!-- <el-table-column label="合同状态更新时间" align="center" width="170" prop="contractStatusTime"
+              v-if="financial.tabelColumnShowArr.includes('contractStatusTime')"></el-table-column> -->
+              <el-table-column label="已开票金额" align="center" prop="invoiced_amount" width="100">
+                <template #default="{row}">
+                  <span style="color: var(--themeColor);cursor: pointer;" @click="invoiceDetail(row)">{{row.invoiced_amount}}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="已到款金额" align="center" prop="payment_amount" width="100">
+                <template #default="{row}">
+                  <span style="color: var(--themeColor);cursor: pointer;" @click="refundDetail(row)">{{row.payment_amount}}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip
+              v-if="financial.tabelColumnShowArr.includes('remark')"></el-table-column>
+              <el-table-column label="登记状态" align="center" prop="register_status" width="90">
+                <template #default="{row}">
+                  {{statusArray[row.register_status-1].label}}
+                </template>
+              </el-table-column>
+              <el-table-column label="操作"  fixed="right" width="120">
+                <template #header>
+                  <div class="table-column-select">
+                    <span>操作</span>
+                    <el-popover title="筛选" trigger="click" @before-enter="initColumnCheckedArr"
+                    v-model:visible="financial.showColumnCheck">
+                      <template #reference>
+                        <svg-Icon name="svgIcon-financial-tableColumnpPick" 
+                        style="font-size: 12px;margin-left: 10px;cursor: pointer;outline: none;"></svg-Icon>                      
+                      </template>
+                      <el-checkbox-group v-model="financial.tabelColumnCheckedArr">
+                        <el-checkbox label="contract_type">合同类型</el-checkbox>
+                        <el-checkbox label="contractDate">合同有效期</el-checkbox>
+                        <el-checkbox label="agreed_pay_time">约定付款时间</el-checkbox>
+                        <el-checkbox label="sign_date">签订日</el-checkbox>
+                        <!-- <el-checkbox label="contractStatusTime">合同状态更新时间</el-checkbox> -->
+                        <el-checkbox label="remark">备注</el-checkbox>
+                      </el-checkbox-group>
+                      <el-button style="float: right;" @click="checkedConfirm">确认</el-button>
+                    </el-popover>
+                  </div>
+                </template>
+                <template #default="{row}">
+                  <div class="table-options">
+                      <span class="table-option-buttons"
+                      @click="registration('view',row.contract_register_id)">
+                        查看
+                      </span>
+                      <span class="table-option-buttons" v-permission="'financial:list:complianceEdit'"
+                      @click="registration('compliance',row.contract_register_id)">
+                        编辑
+                      </span>
+                      <span class="table-option-buttons"  v-permission="'financial:list:delete'"
+                      @click="delteRecord(row)" style="color:#FF3400;">
+                        删除
+                      </span>
+                      <span class="table-option-buttons" v-permission="'financial:list:invoice'"
+                      @click="registration('invoice',row.contract_register_id)">
+                        开票登记
+                      </span>
+                      <span class="table-option-buttons" v-permission="'financial:list:placement'"
+                      @click="registration('placement',row.contract_register_id)">
+                        到款登记
+                      </span>
+                      <span class="table-option-buttons" @click="changeContractStatus(row)">
+                        更改合同状态
+                      </span>
+                    </div>
+                </template>
+              </el-table-column>
+              <template #empty>
+                <div class="table-no-data">
+                  <img src="@/assets/img/icon/empty-data.png" />
+                  <span>暂无数据</span>
+                </div>
+              </template>
+            </el-table>
+            <!-- 分页 -->
+            <m-page :pageSize="financial.searchParams.page_size" :page_no="financial.searchParams.current" 
+            style="display: flex;justify-content: flex-end;margin-top: 20px;" 
+            :total="financial.total" @handleCurrentChange="changePageNo" @handleSizeChange="changePageSize"/>
+          </div>
+        </div>
+        <!-- 开票详情弹窗 -->
+        <el-dialog v-model="dialog.invoiceDetailShow" title="开票详情" width="500px" :close-on-click-modal="false">
+          <el-table :data="dialog.invoiceDetailList" border max-height="600px">
+            <el-table-column label="开票日" prop="invoice_time" align="center"></el-table-column>
+            <el-table-column label="开票金额(元)" prop="amount" align="center"></el-table-column>
+          </el-table>
+        </el-dialog>
+        <!-- 到款详情弹窗 -->
+        <el-dialog v-model="dialog.refundDetailShow" title="到款详情" width="500px" :close-on-click-modal="false">
+          <el-table :data="dialog.refundDetailList" border max-height="600px">
+            <el-table-column label="到款月" prop="invoice_time" align="center"></el-table-column>
+            <el-table-column label="到款金额(元)" prop="amount" align="center"></el-table-column>
+          </el-table>
+        </el-dialog>
+        <!-- 更改合同状态弹窗 -->
+        <el-dialog v-model="dialog.changeContractStatusShow" title="更改合同状态" width="500px" :close-on-click-modal="false">
+          <template style="display: flex;justify-content: center;">
+            <el-form :model="dialog.contractStatusForm" ref="changeStatusForm">
+              <el-form-item label="合同状态" prop="contract_status" :rules="{required:true,message:'请选择合同状态',trigger:'change'}">
+                <el-select v-model="dialog.contractStatusForm.contract_status" placeholder="请选择合同状态" style="width: 286px;">
+                  <el-option :label="item.label" :value="item.id" v-for="item in contractStatusArray" :key="item.id"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-form>
+          </template>
+          <template #footer>
+            <div>
+              <el-button @click="dialog.changeContractStatusShow=false">取消</el-button>
+              <el-button type="primary" @click="submitForm">确定</el-button>
+            </div>
+          </template> 
+        </el-dialog>
+      </template>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .financial-list-container{
+    height: 100%;
+    .financial-search-zone{
+      display: flex;
+      align-items: center;
+      margin-bottom: 20px;
+      flex-wrap: wrap;
+      *{
+        margin-right: 30px;
+        &:last-child{
+          margin-right: 0;
+        }
+      }
+    }
+    .financial-top-option-zone{
+      display: flex;
+      align-items: center;
+    }
+    .financial-table-zone{
+      margin-top: 20px;
+    }
+  }
+</style>
+<style lang="scss">
+  #financial-list-container{
+    .el-dialog__footer{
+      padding-top: 0!important;
+      padding: 0 0 24px 0;
+    }
+    .el-date-editor{
+      .el-input__wrapper{
+        width: 286px;
+      }
+    }
+  }
+
+</style>

+ 238 - 0
src/views/systemManagement/departmentM.vue

@@ -0,0 +1,238 @@
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage,ElMessageBox } from 'element-plus';
+import {getDepartmentList,addDepartment,editDepartment,deleteDepartment} from '@/api/systemMana'
+
+  const department=reactive({
+    departmentSearch:'',
+    searchParams:{
+      dept_name:'',
+      page_size:10,
+      current:1,
+    },
+    total:0,
+    showDialog:false,
+    dialogTitle:"添加部门",
+    // ---------添加弹窗
+    addForm:{
+      parent_id:0,
+      dept_name:'',
+      sort:0
+    },
+    tableData:[]
+  })
+  const addDepartmentForm = ref()
+//  --------------------------method
+  // 获取部门列表
+  const departmentList=()=>{
+    getDepartmentList(department.searchParams).then(res=>{
+      if(res.code == 200){
+        department.tableData = res.data.list || []
+        department.total = res.data.page.total
+      }
+    })
+  }
+  // 改变页码
+  const changePageNo = (pageNo)=>{
+    department.searchParams.current = pageNo
+    departmentList()
+  }
+  // 切换每页的数量
+  const changePageSize=(pageSize)=>{
+    department.searchParams.page_size = pageSize
+    departmentList()
+  }
+  // 添加部门
+  const addDept=(row)=>{
+    department.dialogTitle='添加部门'
+    if(row){
+      department.addForm.parent_id=row.dept_id
+    }
+    department.showDialog=true
+  }
+  // 编辑部门
+  const editDept=(row)=>{
+    department.dialogTitle='编辑部门'
+    department.addForm.parent_id=row.parent_id
+    department.addForm.dept_name=row.dept_name
+    department.addForm.dept_id=row.dept_id
+    department.addForm.sort = row.sort
+    department.showDialog=true
+  }
+  // 搜索
+  const searchDepartment=()=>{
+    department.searchParams.current=1
+    departmentList()
+  }
+  // 刷新列表
+  const refreshList=()=>{
+    department.searchParams.dept_name=''
+    department.searchParams.current=1
+    departmentList()
+  }
+  // 提交表单
+  const submitForm=()=>{
+    addDepartmentForm.value.validate((vaild)=>{
+      if(vaild){
+        // 联级选择器选中的为一个 选中值的数组
+        // console.log(department.addForm);
+        if(Array.isArray(department.addForm.parent_id)){
+          department.addForm.parent_id=department.addForm.parent_id[(department.addForm.parent_id.length-1)]
+        }
+        if(department.addForm.dept_id){
+          // 编辑
+          editDepartment(department.addForm).then(res=>{
+            if(res.code==200){
+              ElMessage.success(`${department.dialogTitle}成功`)
+              department.showDialog=false
+              refreshList()
+            }
+          })
+        }else{
+          // 新增
+          addDepartment(department.addForm).then(res=>{
+            if(res.code==200){
+              ElMessage.success(`${department.dialogTitle}成功`)
+              department.showDialog=false
+              refreshList()
+            }
+          })
+        }
+      }
+    })
+  }
+  const deleteDept=(row)=>{
+    // console.log(row);
+    ElMessageBox.confirm("删除后不可恢复,确认删除该部门及子部门吗?",'提示',    
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }).then(res=>{
+      deleteDepartment({dept_id:row.dept_id}).then(res=>{
+        if(res.code==200){
+          ElMessage({
+            type: 'success',
+            message: '删除部门成功',
+          })
+          refreshList()
+        }
+      })
+    }).catch(()=>{})
+  }
+  // 重置表单、清除错误信息
+  const closeDia=()=>{
+    department.addForm={
+      parent_id:0,
+      dept_name:'',
+      sort:0
+    }
+    setTimeout(()=>{
+      // 清除错误信息
+      addDepartmentForm.value.clearValidate()
+    },0)
+  }
+  //  ---------------------created
+  departmentList()
+  const action = sessionStorage.getItem('deptOpenDialog')
+  if(action){
+    sessionStorage.removeItem('deptOpenDialog')
+    department.showDialog=true
+  } 
+</script>
+
+<template>
+    <div class="system-department-container">
+      <div class="department-container">
+        <div class="department-container-header">
+          <!-- 顶部按钮 -->
+          <div class="department-container-header-buttons">
+            <el-button type="primary" @click="addDept('')" class="header-optios-buttons" v-permission="'system:dept:add'">添加部门</el-button>
+          </div>
+          <!-- 搜索区域 -->
+          <div class="department-container-header-search">
+            <el-input v-model="department.searchParams.dept_name" placeholder="部门名称" :prefix-icon="Search"
+            style="width: 400px;margin-right: 20px;" @input="searchDepartment" />
+          </div>
+        </div>
+        <div class="department-container-right-table">
+          <!-- 表格 -->
+          <el-table :data="department.tableData" border max-height="690px" default-expand-all
+          :tree-props="{hasChildren: 'hasChildren', children: 'Children'}" row-key="dept_id" style="position: sticky;"> 
+            <el-table-column label="部门名称" show-overflow-tooltip prop="dept_name"></el-table-column>
+            <el-table-column label="排序" show-overflow-tooltip prop="sort"></el-table-column>
+            <el-table-column label="创建时间" show-overflow-tooltip prop="create_time"></el-table-column>
+            <el-table-column label="操作">
+              <template #default="scope" >
+                <div class="table-options">
+                  <span class="table-option-buttons" @click="editDept(scope.row)" v-permission="'system:dept:edit'">
+                    编辑
+                  </span>
+                  <span class="table-option-buttons" @click="addDept(scope.row)" v-permission="'system:dept:add'">
+                    添加
+                  </span>
+                  <span class="table-option-buttons" style="color:#C54322;" @click="deleteDept(scope.row)" v-permission="'system:dept:delete'">
+                    删除
+                  </span>
+                </div>
+              </template>
+            </el-table-column>
+            <template #empty>
+              <div class="table-no-data">
+                <img src="@/assets/img/icon/empty-data.png" />
+                <span>暂无数据</span>
+              </div>
+            </template>
+          </el-table>
+          <!-- 分页 -->
+          <m-page :pageSize="department.searchParams.page_size" :page_no="department.searchParams.current" 
+          style="display: flex;justify-content: flex-end;margin-top: 20px;" 
+          :total="department.total" @handleCurrentChange="changePageNo" @handleSizeChange="changePageSize"/>
+        </div>
+      </div>
+      <!-- 添加/编辑部门弹窗 -->
+      <el-dialog v-model="department.showDialog" :title="department.dialogTitle" width="500px" :close-on-click-modal="false"
+      @closed="closeDia">
+        <el-form ref="addDepartmentForm" :model="department.addForm" label-position="right" label-width="88px" 
+        style="padding-left: 45px;">
+          <el-form-item prop="parent_id" label="上级部门">
+            <el-cascader :options="department.tableData" v-model="department.addForm.parent_id" 
+            :props="{value:'dept_id',label:'dept_name',children:'Children',checkStrictly: true,}" style="width: 286px;"
+            ></el-cascader>
+          </el-form-item>
+          <el-form-item prop="dept_name" label="部门名称" :rules="[{required:true,message:'部门名称不能为空',trigger:'blur'}]">
+            <el-input v-model="department.addForm.dept_name" placeholder="请输入部门名称" style="width: 286px;"></el-input>
+          </el-form-item>
+          <el-form-item prop="sort" label="显示排序">
+            <el-input-number v-model="department.addForm.sort" :min="0" controls-position="right" style="width: 286px;"></el-input-number>
+          </el-form-item>
+        </el-form>
+        <template #footer>
+          <div>
+            <el-button @click="department.showDialog=false">退出</el-button>
+            <el-button type="primary" @click="submitForm">保存</el-button>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .system-department-container{
+    height: 100%;
+    .department-container{
+      overflow: hidden;
+      .department-container-header{
+        display: flex;
+        justify-content: space-between;
+        .department-container-header-search{
+          display: flex;
+          align-items: center;
+        }
+      }
+      .department-container-right-table{
+        margin-top: 20px;
+      }
+    }
+  }
+</style>

+ 430 - 0
src/views/systemManagement/menuM.vue

@@ -0,0 +1,430 @@
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage,ElMessageBox } from 'element-plus';
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import {getMenuList,editMenu,deleteMenu,addMenu} from '@/api/systemMana'
+
+// 获取所有的自定义图标id
+import ids from 'virtual:svg-icons-names'
+
+// 拿出其中menu文件夹的图标
+const menuIcons = Array.isArray(ids)?ids.filter(item => item.split('-')[1]=='menu'):[]
+// console.log(menuIcons,'menuIcons');
+
+// 所有图标
+const IconsAll=Object.keys(ElementPlusIconsVue).map(key=> key)
+
+  const menu=reactive({
+    menuSearch:'',
+    searchParams:{
+      name:'',
+      page_size:10,
+      current:1
+    },
+    tableData:[],
+    onlyMenuList:[],
+    total:0,
+    showDialog:false,
+    dialogTitle:"添加部门",
+    // ---------添加弹窗
+    addForm:{
+      parent_id:0,
+      name:'',
+      sort:0,
+      icon_path:'',
+      path_name:'',
+      hidden:0,
+      hidden_layout:0,
+      path:'',
+      component:'',
+      button_code:'',
+      menu_type: 0
+    },
+    showIconSelect:false,
+  })
+  const addmenuForm = ref()
+  const menuStatus=['显示','隐藏']
+
+//  --------------------------method
+  // 获取资源列表
+  const menuList=()=>{
+    getMenuList(menu.searchParams).then(res=>{
+      if(res.code == 200){
+        menu.tableData = res.data.list || []
+        menu.total = res.data.page.total
+      }
+    })
+  }
+  // 获取前二级菜单的菜单列表不包括按钮
+  const menuListOnly=()=>{
+    getMenuList({hide_level3:1,page_size:1000,current:1,hide_button:1}).then(res=>{
+      if(res.code == 200){
+        menu.onlyMenuList = res.data.list || []
+        // console.log(menu.onlyMenuList);
+      }
+    })
+  }
+  const changePageNo = (pageNo)=>{
+    menu.searchParams.current = pageNo
+    menuList()
+  }
+  // 切换每页的数量
+  const changePageSize=(pageSize)=>{
+    menu.searchParams.page_size = pageSize
+    menuList()
+  }
+  // 刷新列表
+  const refreshList=()=>{
+    menu.searchParams.name=''
+    menu.searchParams.current=1
+    menuList()
+    menuListOnly()
+  }
+
+  const addmenu=(row)=>{
+    menu.dialogTitle='添加菜单'
+    if(row){
+      menu.addForm.parent_id=row.menu_id
+    }
+    menu.showDialog=true
+  }
+
+  const editmenu=(row)=>{
+    menu.dialogTitle='编辑菜单'
+    menu.addForm={
+      menu_id:row.menu_id,
+      parent_id:row.parent_id,
+      name:row.name,
+      sort:row.sort,
+      icon_path:row.icon_path,
+      path:row.path,
+      path_name:row.path_name,
+      hidden:row.hidden,
+      hidden_layout:row.hidden_layout,
+      component:row.component,
+      button_code:row.button_code,
+      menu_type: row.menu_type
+    },
+    menu.showDialog=true
+  }
+
+  // 选择图标
+  const selectIcon=(e)=>{
+    // console.log(e);
+    if(e.target.dataset.set){
+      menu.addForm.icon_path = e.target.dataset.set
+      menu.showIconSelect=false
+    }else if(e.target.parentNode.dataset.set){
+      menu.addForm.icon_path = e.target.parentNode.dataset.set
+      menu.showIconSelect=false
+    }
+  }
+
+  const searchmenu=()=>{
+    menu.searchParams.current = 1
+    menuList()
+  }
+
+  // 切换菜单乐行 0菜单 1按钮 
+  const changeMenuType=(value)=>{
+    if(value==0){
+      menu.addForm.button_code=''
+    }else{
+      menu.addForm.icon_path=''
+      menu.addForm.component=''
+      menu.addForm.path=''
+      menu.addForm.path_name=''
+    }
+  }
+
+  // 提交表单
+  const submitForm=()=>{
+    addmenuForm.value.validate((vaild)=>{
+      if(vaild){
+        // 联级选择器选中的为一个 选中值的数组
+        if(Array.isArray(menu.addForm.parent_id)){
+          menu.addForm.parent_id=menu.addForm.parent_id[(menu.addForm.parent_id.length-1)]
+        }
+        // console.log(menu.addForm);
+        if(menu.addForm.menu_id){
+          // 编辑
+          editMenu(menu.addForm).then(res=>{
+            if(res.code==200){
+              ElMessage.success(`${menu.dialogTitle}成功`)
+              menu.showDialog=false
+              // refreshList()
+              location.reload()
+            }
+          })
+        }else{
+          // 新增
+          addMenu(menu.addForm).then(res=>{
+            if(res.code==200){
+              ElMessage.success(`${menu.dialogTitle}成功`)
+              menu.showDialog=false
+              refreshList()
+            }
+          })
+        }
+      }
+    })
+  }
+  const removeMenu=(row)=>{
+    // console.log(row);
+    ElMessageBox.confirm("删除后不可恢复,确认删除该菜单及子菜单吗?",'提示',    
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }).then(res=>{
+      deleteMenu({menu_id:row.menu_id}).then(res=>{
+        if(res.code==200){
+          ElMessage({
+            type: 'success',
+            message: '删除菜单成功',
+          })
+          refreshList()
+        }
+      })
+    }).catch(()=>{})
+  }
+  // 重置表单、清除错误信息
+  const closeDia=()=>{
+    menu.addForm={
+      parent_id:0,
+      name:'',
+      sort:0,
+      icon_path:'',
+      path:'',
+      path_name:'',
+      hidden:0,
+      hidden_layout:0,
+      component:'',
+      button_code:'',
+      menu_type: 0
+    }
+    setTimeout(()=>{
+      // 清除错误信息
+      addmenuForm.value.clearValidate()
+    },0)
+  }
+  // -------------------------created
+  menuList()
+  // 只有前两级菜单且不包括按钮
+  menuListOnly()
+</script>
+
+<template>
+    <div class="system-menu-container">
+      <div class="menu-container">
+        <div class="menu-container-header">
+          <div class="menu-container-header-buttons">
+            <el-button type="primary" @click="addmenu('')" class="header-optios-buttons" v-permission="'system:menu:add'">
+              添加菜单
+            </el-button>
+          </div>
+          <!-- 搜索区域 -->
+          <div class="menu-container-header-search">
+            <el-input v-model="menu.searchParams.name" placeholder="菜单名称" :prefix-icon="Search"
+            style="width: 286px;" @input="searchmenu" />
+          </div>
+        </div>
+        <div class="menu-container-right-table">
+          <!-- 表格 -->
+          <el-table :data="menu.tableData" border max-height="690px" row-key="menu_id" style="position: sticky;"
+          default-expand-all> 
+            <el-table-column label="菜单名称" show-overflow-tooltip prop="name"></el-table-column>
+            <el-table-column label="图标" align="center" show-overflow-tooltip prop="icon">
+              <template #default="scope">
+                <el-icon size="18px" color="#000" v-if="scope.row.icon_path"
+                style="top: 50%;left: 50%;position: absolute;transform: translateX(-50%) translateY(-50%);">
+                  <svg-icon :name="scope.row.icon_path" color="#000" v-if="scope.row.icon_path.startsWith('svgIcon')"/>
+                  <component :is="scope.row.icon_path" v-else/>
+                </el-icon>
+                <span v-else>--</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="排序" show-overflow-tooltip prop="sort"></el-table-column>
+            <el-table-column label="路由名称" show-overflow-tooltip prop="path_name">
+              <template #default="scope">
+                <span v-if="scope.row.path_name">{{scope.row.path_name}}</span>
+                <span v-else>--</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="组件路径" show-overflow-tooltip prop="component">
+              <template #default="scope">
+                <span v-if="scope.row.component">{{scope.row.component}}</span>
+                <span v-else>--</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="是否隐藏" show-overflow-tooltip prop="hidden">
+              <template #default="scope">
+                <span :style="{'color':scope.row.hidden==0?'':'#23AE0C'}">{{menuStatus[scope.row.hidden]}}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="创建时间" show-overflow-tooltip prop="create_time">
+              <template #default="scope">
+                <span v-if="scope.row.create_time">{{scope.row.create_time}}</span>
+                <span v-else>--</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作">
+              <template #default="scope" >
+                <div class="table-options">
+                  <span class="table-option-buttons" @click="addmenu(scope.row)" 
+                  v-if="scope.row.menu_type==0 && scope.row.level<2" v-permission="'system:menu:add'">
+                    添加
+                  </span>
+                  <span class="table-option-buttons" @click="editmenu(scope.row)" v-permission="'system:menu:edit'">编辑</span>
+                  <span class="table-option-buttons" style="color:#C54322;" @click="removeMenu(scope.row)" v-permission="'system:menu:delete'">删除</span>
+                </div>
+              </template>
+            </el-table-column>
+            <template #empty>
+              <div class="table-no-data">
+                <img src="@/assets/img/icon/empty-data.png" />
+                <span>暂无数据</span>
+              </div>
+            </template>
+          </el-table>
+          <!-- 分页 -->
+          <m-page :page_size="10" :page_no="menu.searchParams.current" 
+          style="display: flex;justify-content: flex-end;margin-top: 20px;" 
+          :total="menu.total" @handleCurrentChange="changePageNo" @handleSizeChange="changePageSize"/>
+        </div>
+      </div>
+      <!-- 添加/编辑菜单弹窗 -->
+      <el-dialog v-model="menu.showDialog" :title="menu.dialogTitle" width="500px" :close-on-click-modal="false"
+      @closed="closeDia" top="12vh">
+        <el-form ref="addmenuForm" :model="menu.addForm" style="padding-left: 15px;"
+        id="add-form" label-position="right" label-width="118px"  >
+          <el-form-item prop="parentEmnu" label="上级菜单">
+            <el-cascader :options="menu.onlyMenuList" v-model="menu.addForm.parent_id" placeholder="请选择上级菜单"
+            :props="{value:'menu_id',label:'name',children:'children',checkStrictly: true}" style="width: 286px;"
+            ></el-cascader>
+          </el-form-item>
+          <el-form-item prop="name" label="菜单标题" :rules="[{required:true,message:'菜单标题不能为空',trigger:'blur'}]">
+            <el-input v-model="menu.addForm.name" placeholder="请输入菜单标题" style="width: 286px;"></el-input>
+          </el-form-item>
+          <el-form-item prop="sort" label="显示排序">
+            <el-input-number v-model="menu.addForm.sort" :min="0" 
+            controls-position="right" style="width: 286px;"></el-input-number>
+          </el-form-item>
+          <el-form-item prop="menu_type" label="菜单类型" style="width: 286px;"
+          :rules="[{required:true,message:'菜单类型不能为空',trigger:'change'}]">
+            <el-radio-group v-model="menu.addForm.menu_type" @change="changeMenuType">
+              <el-radio :label="0">菜单</el-radio>
+              <el-radio :label="1">按钮</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <template v-if="menu.addForm.menu_type==0">
+            <el-form-item prop="icon_path" label="菜单图标">
+              <el-popover width="978px" trigger="click" v-model:visible="menu.showIconSelect"
+              :teleported="false">
+                <template #reference>
+                  <el-icon size="24px" style="cursor: pointer;" v-if="menu.addForm.icon_path">
+                    <!-- 自定图标 -->
+                    <svg-icon :name="menu.addForm.icon_path" color="#000" v-if="menu.addForm.icon_path.startsWith('svgIcon')"/>
+                    <!-- ElementPlus图标 -->
+                    <component :is="menu.addForm.icon_path" v-else/>
+                  </el-icon>
+                  <span v-else style="cursor: pointer;">点击选择菜单图标</span>
+                </template>
+                <template #default>
+                  <el-tabs>
+                    <el-tab-pane label="ElementPlus">
+                      <div @click="selectIcon">
+                        <el-icon size="24px" class="select-icon" color="#000" v-for="item in IconsAll" :key="item">
+                          <component :is="item" :data-set="item" />
+                        </el-icon>
+                      </div>
+                    </el-tab-pane>
+                    <el-tab-pane label="自定义">
+                      <div @click="selectIcon">
+                        <el-icon size="24px" class="select-icon"  v-for="item in menuIcons" :key="item">
+                          <svg-icon :name="item" :data-set="item" color="#000"/>
+                        </el-icon>
+                      </div>
+                    </el-tab-pane>
+                  </el-tabs>
+                </template>
+              </el-popover>
+            </el-form-item>
+            <el-form-item prop="path_name" label="路由名称">
+              <el-input v-model="menu.addForm.path_name" placeholder="请输入路由名称" style="width: 286px;"></el-input>
+            </el-form-item>
+            <el-form-item prop="path" label="路由地址">
+              <el-input v-model="menu.addForm.path" placeholder="请输入路由地址" style="width: 286px;"></el-input>
+            </el-form-item>
+            <el-form-item prop="component" label="组件路径">
+              <el-input v-model="menu.addForm.component" placeholder="请输入组件路径" style="width: 286px;"></el-input>
+            </el-form-item>
+            <el-form-item prop="hidden" label="是否隐藏" style="width: 286px;"
+             :rules="[{required:true,message:'是否隐藏不能为空',trigger:'change'}]">
+              <el-radio-group v-model="menu.addForm.hidden">
+                <el-radio :label="0">显示</el-radio>
+                <el-radio :label="1">隐藏</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item prop="hidden_layout" label="是否有Layout" style="width: 286px;"
+            :rules="[{required:true,message:'是否有Layout不能为空',trigger:'change'}]">
+              <el-radio-group v-model="menu.addForm.hidden_layout">
+                <el-radio :label="0">是</el-radio>
+                <el-radio :label="1">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </template>
+          <el-form-item prop="button_code" label="按钮ID" v-else>
+              <el-input v-model="menu.addForm.button_code" style="width: 286px;"
+              placeholder="请输入按钮ID"></el-input>
+            </el-form-item>
+        </el-form>
+        <template #footer>
+          <div>
+            <el-button @click="menu.showDialog=false">退出</el-button>
+            <el-button type="primary" @click="submitForm">保存</el-button>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .system-menu-container{
+    height: 100%;
+    .menu-container{
+      overflow: hidden;
+      .menu-container-header{
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        .menu-container-header-search{
+          display: flex;
+          align-items: center;
+        }
+      }
+      .menu-container-right-table{
+        margin-top: 20px;
+      }
+    }
+  }
+  .select-icon{
+    margin: 5px;
+    cursor: pointer;
+  }
+</style>
+<style lang="scss">
+  @media screen and (max-width:978px) {
+    #add-form{
+      .el-popover.el-popper{
+        width: 720px!important;
+        // max-height: 360px;
+        // overflow-y: auto;
+      }
+      .el-tabs__content{
+        max-height: 320px;
+        overflow-y: auto;
+      }
+    }
+  }
+</style>

+ 207 - 0
src/views/systemManagement/roleM.vue

@@ -0,0 +1,207 @@
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { ElMessage,ElMessageBox } from 'element-plus';
+import {getRoleList,addRoleApi,deleteRoleApi} from '@/api/systemMana'
+import {useRouter,useRoute} from 'vue-router'
+
+const router = useRouter()
+const route = useRoute()
+  const role=reactive({
+    searchParams:{
+      role_name:'',
+      page_size:10,
+      current:1,
+    },
+    tableData:[],
+    total:0,
+    showDialog:false,
+    // ---------添加弹窗
+    addForm:{
+      role_name:''
+    }
+  })
+  const addRoleForm = ref()
+  const showRouterView = ref(false)
+//  --------------------------method
+  // 获取角色列表
+  const roleList=()=>{
+    getRoleList(role.searchParams).then(res=>{
+      if(res.code == 200){
+        role.tableData = res.data.list || []
+        role.total = res.data.page.total
+      }
+    })
+  }
+  // 切换每页的数量
+  const changePageSize=(pageSize)=>{
+    role.searchParams.page_size = pageSize
+    roleList()
+  }
+  const changePageNo = (pageNo)=>{
+    role.searchParams.current = pageNo
+    roleList()
+  }
+  const searchRole=()=>{
+    role.searchParams.current = 1
+    roleList()
+  }
+  // 设置权限
+  const premission=(type,id='')=>{
+    showRouterView.value=true
+    router.push({path:'/system/role/setPermission',query:{perssiomType:type,role_id:id}})
+  }
+  // 刷新列表
+  const refreshList=()=>{
+    role.searchParams.role_name=''
+    role.searchParams.current=1
+    roleList()
+  }
+  // 提交
+  const submitForm=()=>{
+    addRoleForm.value.validate((vaild)=>{
+      if(vaild){
+        addRoleApi(role.addForm).then(res=>{
+          if(res.code==200){
+            ElMessage.success('添加角色成功')
+            role.showDialog=false
+            refreshList()
+          }
+        })
+      }
+    })
+  }
+  const deleteRole=(row)=>{
+    console.log(row);
+    ElMessageBox.confirm("删除后不可恢复,确认删除该角色吗?",'提示',    
+    {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }).then(res=>{
+      deleteRoleApi({role_id:row.role_id}).then(res=>{
+        if(res.code==200){
+          ElMessage.success('删除角色成功')
+          refreshList()
+        }
+      })
+    }).catch(()=>{})
+  }
+  // 重置表单、清除错误信息
+  const closeDia=()=>{
+    role.addForm={
+      role_name:''
+    }
+    setTimeout(()=>{
+      // 清除错误信息
+      addRoleForm.value.clearValidate()
+    },0)
+  }
+
+  //  ---------------------created
+  roleList()
+</script>
+
+<template>
+    <div style="height: 100%;">
+      <!-- 三级路由 -->
+      <router-view></router-view>
+      <!-- 三级路由时,二级路由不显示 -->
+      <div class="system-role-container" id="system-role-container" v-if="route.path=='/system/role'"> 
+        <div class="role-container">
+          <div class="role-container-header">
+            <div class="role-container-header-buttons">
+              <el-button type="primary" @click="role.showDialog=true" class="header-optios-buttons" v-permission="'system:role:add'">
+                添加角色
+              </el-button>
+              <el-button type="primary" @click="premission('set')" class="header-optios-buttons" v-permission="'system:role:setPermission'"
+                style="margin-left: 20px;">
+                设置权限
+              </el-button>
+            </div>
+            <!-- 搜索区域 -->
+            <div class="role-container-header-search">
+              <el-input v-model="role.searchParams.role_name" placeholder="角色搜索" :prefix-icon="Search"
+              style="width: 400px;" @input="searchRole" />
+            </div>
+          </div>
+          <div class="role-container-right-table">
+            <!-- 表格 -->
+            <el-table :data="role.tableData" border max-height="690px" style="position: sticky;"> 
+              <el-table-column label="序号" show-overflow-tooltip align="center" prop="role_id"></el-table-column>
+              <el-table-column label="角色" show-overflow-tooltip align="center" prop="role_name"></el-table-column>
+              <el-table-column label="创建时间" show-overflow-tooltip align="center" prop="create_time"></el-table-column>
+              <el-table-column label="操作" align="center">
+                <template #default="scope" >
+                  <div class="table-options">
+                    <span class="table-option-buttons" @click="premission('view',scope.row.role_id)" 
+                      v-permission="'system:role:viewPermission'">
+                      查看权限
+                    </span>
+                    <span class="table-option-buttons" style="color:#C54322;" 
+                    @click="deleteRole(scope.row)" v-permission="'system:role:delete'">
+                      删除
+                    </span>
+                  </div>
+                </template>
+              </el-table-column>
+              <template #empty>
+                <div class="table-no-data">
+                  <img src="@/assets/img/icon/empty-data.png" />
+                  <span>暂无数据</span>
+                </div>
+              </template>
+            </el-table>
+            <!-- 分页 -->
+            <m-page :pageSize="role.searchParams.page_size" :page_no="role.searchParams.current" 
+            style="display: flex;justify-content: flex-end;margin-top: 20px;" 
+            :total="role.total" @handleCurrentChange="changePageNo" @handleSizeChange="changePageSize"/>
+          </div>
+        </div>
+        <!-- 添加角色弹窗 -->
+        <el-dialog v-model="role.showDialog" title="添加角色" width="400px" :close-on-click-modal="false"
+        @closed="closeDia">
+          <el-form ref="addRoleForm" :model="role.addForm">
+            <el-form-item prop="role_name" label="角色名称" :rules="[{required:true,message:'角色名称不能为空',trigger:'blur'}]">
+              <el-input v-model="role.addForm.role_name" placeholder="请输入角色名称"></el-input>
+            </el-form-item>
+          </el-form>
+          <template #footer>
+            <div>
+              <el-button @click="role.showDialog=false" style="height: 32px;width:80px;">取消</el-button>
+              <el-button type="primary" @click="submitForm" style="height: 32px;width:80px;">保存</el-button>
+            </div>
+          </template>
+        </el-dialog>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .system-role-container{
+    height: 100%;
+    .role-container{
+      overflow: hidden;
+      .role-container-header{
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        .role-container-header-search{
+          display: flex;
+          align-items: center;
+        }
+      }
+      .role-container-right-table{
+        margin-top: 30px;
+      }
+    }
+  }
+</style>
+<style lang="scss">
+  #system-role-container{
+    .el-dialog__footer{
+      padding-top: 0!important;
+      padding: 0 0 24px 0;
+    }
+  }
+
+</style>

+ 435 - 0
src/views/systemManagement/rolePermission.vue

@@ -0,0 +1,435 @@
+<script setup>
+import { useRoute,useRouter} from 'vue-router'
+import {getRoleList,getRolePermission,setRolePermission} from '@/api/systemMana'
+import { ElMessage } from 'element-plus';
+const route = useRoute()
+const router = useRouter()
+
+  const rolePermisson=reactive({
+    currentRole:{
+      role_id:0
+    },
+    roleList:[],
+    permissionData:[],
+    checkList:[]
+  })
+  // 合并单元格时,每列的行数
+  let rowNumArray=[]
+  let position = 0
+
+//  --------------------method
+  const rolePermissionBind=async ()=>{
+    await getRole()
+    permissionBind()
+  }
+
+// 获取角色列表
+  const getRole=async()=>{
+    let params={
+      page_size:10000,
+      current:1,
+      role_name:''
+    }
+    // 获取角色列表
+    await getRoleList(params).then(res=>{
+      console.log('aaa');
+      if(res.code == 200){
+        rolePermisson.roleList=res.data.list || []
+        // role_id 路由参数有取路由参数 没有取列表第一个
+        rolePermisson.currentRole.role_id = rolePermisson.currentRole.role_id || rolePermisson.roleList[0].role_id
+      }
+    })
+  }
+
+// 获取角色绑定列表
+  const permissionBind=()=>{
+    getRolePermission({role_id:rolePermisson.currentRole.role_id}).then(res=>{
+      let dataList = res.data.list || []
+      rolePermisson.checkList = res.data.choice_list || []
+      // 重置
+      rolePermisson.permissionData=[]
+      // 将目录数据转化成表格的数据结构
+      dataList.map(item =>{
+        // 一级
+        if(item.component){
+          // 有component是一级菜单,不是目录
+          rolePermisson.permissionData.push({
+            levelOne:{
+              name:item.name,
+              menu_id:item.menu_id,
+              checkAll:false,
+              indeterminate:false
+            },
+            levelTwo:{
+              name:item.name,
+              has_bind:item.has_bind,
+              menu_id:item.menu_id,
+              checkAll:false,
+              indeterminate:false
+            },
+            levelThree:[]
+          })
+        }
+        item.children.map(ite => {
+          // 二级
+          if(ite.menu_type==0){
+            // 菜单
+            rolePermisson.permissionData.push({
+              levelOne:{
+                name:item.name,
+                menu_id:item.menu_id,
+                checkAll:false,
+                indeterminate:false
+              },
+              levelTwo:{
+                name:ite.name,
+                menu_id:ite.menu_id,
+                checkAll:false,
+                indeterminate:false
+              },
+              levelThree:[]
+            })
+          }else{
+            // 按钮 -- 所有的按钮都放在三级
+            rolePermisson.permissionData.find(a => a.levelOne.menu_id==ite.parent_id).levelThree.push({
+              name:ite.name,
+              menu_id:ite.menu_id,
+              menu_type:ite.menu_type
+            })
+          }
+          let pushIndex=rolePermisson.permissionData.findIndex(b => b.levelTwo.menu_id == ite.menu_id)
+          ite.children.map(it => {
+            // 三级
+            rolePermisson.permissionData[pushIndex].levelThree.push({
+              name:it.name,
+              menu_id:it.menu_id,
+              menu_type:it.menu_type
+            })
+          })
+        })
+      })
+      // 设置合并单元参数
+      setSpanRow()
+      // 将checkList里面的Id映射到页面的一二级复选框
+      setCheckListToPage()
+    })
+  }
+
+  const setCheckListToPage=()=>{
+    rolePermisson.permissionData.forEach(item =>{
+      if(rolePermisson.checkList.includes(item.levelOne.menu_id)){
+        item.levelOne.checkAll=true
+      }
+      if(rolePermisson.checkList.includes(item.levelTwo.menu_id)){
+        item.levelTwo.checkAll=true
+      }
+      checkALLandInter(item.levelOne.menu_id)
+    })
+  }
+  // 合并单元格的列计算方法
+  const setSpanRow=()=>{
+    // 重置
+    rowNumArray=[]
+    position = 0
+    rolePermisson.permissionData.map((item,index)=>{
+      if (index === 0) {
+        rowNumArray.push(1);
+      } else {
+        if (
+          rolePermisson.permissionData[index].levelOne.name ===
+          rolePermisson.permissionData[index - 1].levelOne.name
+        ) {
+          rowNumArray[position] += 1; //名称相同,合并到同一个数组中
+          rowNumArray.push(0);
+        } else {
+          rowNumArray.push(1);
+          position = index;
+        }
+      }
+    })
+  }
+  // 合并单元格
+  const spanMerge=({rowIndex, columnIndex})=>{
+    if (columnIndex === 0) {
+      const _row = rowNumArray[rowIndex];
+      return {
+        rowspan: _row,
+        colspan: 1,
+      };
+    }
+  }
+
+  const checklevelOne=(value,row)=>{
+    if(value){
+      // 勾选
+      rolePermisson.permissionData.forEach(item =>{
+        if(item.levelOne.menu_id == row.menu_id){
+          item.levelOne.indeterminate=false
+          item.levelOne.checkAll=true
+          // 添加一级Id
+          rolePermisson.checkList.push(item.levelOne.menu_id)
+          // 添加二级Id
+          rolePermisson.checkList.push(item.levelTwo.menu_id)
+          item.levelTwo.indeterminate=false
+          item.levelTwo.checkAll=true
+          // 添加三级Id
+          item.levelThree.map(it=>{
+            rolePermisson.checkList.push(it.menu_id)
+          })
+          // checklevelTwo(value,item.levelTwo,item.levelOne.menu_id)
+        }
+      })
+      // 去重
+      rolePermisson.checkList = [...new Set(rolePermisson.checkList)]
+    }else{
+      rolePermisson.permissionData.forEach(item =>{
+        if(item.levelOne.menu_id == row.menu_id){
+          item.levelOne.indeterminate=false
+          item.levelOne.checkAll=false
+          // 删除一级Id
+          let splicIndex = rolePermisson.checkList.findIndex(elment => elment==item.levelOne.menu_id)
+          if(splicIndex!=-1){
+            rolePermisson.checkList.splice(splicIndex,1)
+          }
+          // 删除二级Id
+          splicIndex = rolePermisson.checkList.findIndex(elment => elment==item.levelTwo.menu_id)
+          if(splicIndex!=-1){
+            rolePermisson.checkList.splice(splicIndex,1)  
+          }
+          item.levelTwo.indeterminate=false
+          item.levelTwo.checkAll=false
+          // 删除三级Id
+          item.levelThree.map(it=>{
+            splicIndex=rolePermisson.checkList.findIndex(elment => elment==it.menu_id)
+            if(splicIndex!=-1){
+              rolePermisson.checkList.splice(splicIndex,1)
+            }
+          })
+        }
+      })
+    }
+  }
+  const checklevelTwo=(value,row,levelOneId)=>{
+    if(value){
+      // 勾选
+      rolePermisson.permissionData.forEach(item =>{
+        if(item.levelTwo.menu_id == row.menu_id){
+          rolePermisson.checkList.push(item.levelTwo.menu_id)
+          item.levelTwo.indeterminate=false
+          item.levelTwo.checkAll=true
+          item.levelThree.map(it=>{
+            rolePermisson.checkList.push(it.menu_id)
+          })
+        }
+      })
+      rolePermisson.checkList = [...new Set(rolePermisson.checkList)]
+    }else{
+      rolePermisson.permissionData.forEach(item =>{
+        if(item.levelTwo.menu_id == row.menu_id){
+          item.levelTwo.indeterminate=false
+          item.levelTwo.checkAll=false
+          let splicIndex = rolePermisson.checkList.findIndex(elment => elment==item.levelTwo.menu_id)
+          if(splicIndex!=-1) rolePermisson.checkList.splice(splicIndex,1)
+          item.levelThree.map(it=>{
+            splicIndex=rolePermisson.checkList.findIndex(elment => elment==it.menu_id)
+            if(splicIndex!=-1) rolePermisson.checkList.splice(splicIndex,1)
+          })
+        }
+      })
+    }
+    checkALLandInter(levelOneId)
+  }
+
+  // 勾选二级Id和三级Id后的 复选框联动判断
+  const checkALLandInter=(levelOneId)=>{
+    let num=0
+    let account=0
+    rolePermisson.permissionData.forEach(item =>{
+      if(item.levelOne.menu_id == levelOneId){
+        if(rolePermisson.checkList.includes(item.levelTwo.menu_id)){
+          num = num+1
+        }
+        account++
+      }
+
+      // 下面的做了三级复选框的联动
+      // if(item.levelOne.menu_id == levelOneId){
+      //   // 没有三级选项
+      //   if(item.levelThree.length==0){
+      //     account++
+      //     if(rolePermisson.checkList.includes(item.levelTwo.menu_id)){
+      //       num = num+2
+      //     }
+      //     return 
+      //   }
+
+      //   let splicIndex=rolePermisson.checkList.findIndex(elment => elment==item.levelTwo.menu_id)
+      //   if(item.levelThree.every(itt => !rolePermisson.checkList.includes(itt.menu_id))){
+      //     //一个都没有
+      //     if(splicIndex!=-1) rolePermisson.checkList.splice(splicIndex,1)
+      //     item.levelTwo.checkAll=false
+      //     item.levelTwo.indeterminate=false
+      //   }else if(item.levelThree.every(itt => rolePermisson.checkList.includes(itt.menu_id))){
+      //     num = num+2
+      //     item.levelTwo.checkAll=true
+      //     item.levelTwo.indeterminate=false
+      //     if(splicIndex==-1) rolePermisson.checkList.push(item.levelTwo.menu_id)
+      //   }else{
+      //     // 有一部分
+      //     item.levelTwo.checkAll=true
+      //     // item.levelTwo.checkAll=false
+      //     // item.levelTwo.indeterminate=true
+      //     num = num+1
+      //   }
+        
+      // }
+    })
+    // console.log(num,account);
+    rolePermisson.permissionData.forEach(item =>{
+      if(item.levelOne.menu_id == levelOneId){
+        let splicIndex=rolePermisson.checkList.findIndex(elment => elment==levelOneId)
+        if(num==account){
+          // 全选
+          item.levelOne.indeterminate=false
+          item.levelOne.checkAll=true
+          if(splicIndex==-1) rolePermisson.checkList.push(item.levelOne.menu_id)
+        }else if(num==0){
+          //全没选
+          item.levelOne.indeterminate=false
+          item.levelOne.checkAll=false
+          if(splicIndex!=-1) rolePermisson.checkList.splice(splicIndex,1)
+        }else{
+          // 部分选
+          item.levelOne.indeterminate=true
+          item.levelOne.checkAll=false
+          if(splicIndex==-1) rolePermisson.checkList.push(item.levelOne.menu_id)
+        }
+      }
+    })
+    // rolePermisson.checkList = [...new Set(rolePermisson.checkList)]
+  }
+
+  // 设置权限
+  const setPermission=()=>{
+    setRolePermission({
+      role_id: rolePermisson.currentRole.role_id,
+      menu_id_list:rolePermisson.checkList}).then(res=>{
+      ElMessage.success('设置成功,对应角色的用户需要刷新页面获取最新权限')
+      // setTimeout(()=>{
+      //   router.back()
+      // },1200)
+    })
+  }
+
+  
+  // --------created
+  // view 查看 set设置
+  let permission = route.query.perssiomType || 'view'
+  rolePermisson.currentRole.role_id = parseInt(route.query.role_id) || ''
+  rolePermissionBind()
+</script>
+
+<template>
+    <div class="role-permisson-contaner" id="role-permisson-contaner">
+      <el-form :model="rolePermisson.currentRole" style="margin-bottom: 30px;">
+        <el-form-item label="角色">
+          <el-select v-model="rolePermisson.currentRole.role_id" placeholder="请选择角色" @change="permissionBind"
+          :disabled="permission=='view'">
+            <el-option :label="item.role_name" :value="item.role_id" v-for="item in rolePermisson.roleList"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div class="role-permisson-box">
+        <p>权限设置</p>
+        <el-scrollbar class="role-permission-table">
+          <el-table :data="rolePermisson.permissionData" border :span-method="spanMerge"
+          :show-header="false" >
+            <el-table-column prop="levelOne" align="center" width="180px">
+              <template #default="scope">
+                <el-checkbox :label="scope.row.levelOne.menu_id" :indeterminate="scope.row.levelOne.indeterminate"
+                  v-model="scope.row.levelOne.checkAll" :disabled="permission=='view'"
+                  @change="checklevelOne($event,scope.row.levelOne)">
+                  <span>
+                    {{scope.row.levelOne.name}}
+                  </span>
+                </el-checkbox>
+              </template>
+            </el-table-column>
+            <el-table-column prop="levelTwo" align="center" width="180px">
+              <template #default="scope">
+                <el-checkbox :label="scope.row.levelTwo.menu_id"  :indeterminate="scope.row.levelTwo.indeterminate" :disabled="permission=='view'"
+                  v-model="scope.row.levelTwo.checkAll" @change="checklevelTwo($event,scope.row.levelTwo,scope.row.levelOne.menu_id)">
+                  <span>
+                    {{scope.row.levelTwo.name}}
+                  </span>
+                </el-checkbox>
+              </template>
+            </el-table-column>
+            <el-table-column prop="levelThree">
+              <template #default="scope">
+                 <el-checkbox-group v-model="rolePermisson.checkList">
+                  <!-- @change="checkALLandInter(scope.row.levelOne.menu_id)" -->
+                   <el-checkbox :label="item.menu_id" 
+                   v-for="item in scope.row.levelThree" :disabled="permission=='view'">
+                    <span>
+                      {{item.name}}{{item.menu_type == 0 ? '(菜单)':''}}
+                    </span>
+                  </el-checkbox>
+                 </el-checkbox-group>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-scrollbar>
+        <div class="permission-button-box">
+          <el-button class="permission-submit-button" @click="router.back()" v-if="permission!='view'">取消</el-button>
+          <el-button type="primary" class="permission-submit-button" @click="setPermission" v-if="permission!='view'">保存</el-button>
+        </div>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .role-permisson-contaner{
+    height: 100%;
+    .role-permisson-box{
+      height: calc(100% - 70px);
+      padding: 20px;
+      box-sizing: border-box;
+      background-color: white;
+      border-radius: 8px;
+      p{
+        font-size: 14px;
+        // font-family: PingFang SC-Medium, PingFang SC;
+        font-weight: bold;
+        color: #333333;
+      }
+      .role-permission-table{
+          margin: 36px 0 0 28px;
+          padding-right: 28px;
+          height: calc(100% - 148px);
+      }
+      .permission-button-box{
+        text-align: center;
+        padding-top: 50px;
+        .permission-submit-button{
+          width: 130px;
+          height: 40px;
+          &:first-child{
+            border: 1px solid $themeColor;
+            color: $themeColor;
+          }
+          &:last-child{
+            margin-left: 30px;
+          }
+        }
+      }
+    }
+  }
+</style>
+<style lang="scss">
+  #role-permisson-contaner{
+    .el-checkbox{
+      margin-right: 10px;
+    }
+  }
+</style>

+ 545 - 0
src/views/systemManagement/userM.vue

@@ -0,0 +1,545 @@
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import {ElMessage,ElMessageBox} from "element-plus"
+import {getuserList,getDepartmentList,getRoleList,addUserApi,editUserApi,
+  editUserStatus,resetUserPsd,deleteUserApi} from '@/api/systemMana'
+import { useRouter } from 'vue-router';
+import md5 from 'js-md5'
+
+const router=useRouter()
+const userStatus=[{value:1,label:'启用'},{value:0,label:'禁用'}]
+
+  const submitFormEl = ref()
+  const resetPsdFormEl = ref()
+  const user=reactive({
+    searchParams:{
+      key_word:'',
+      dept_id:'',
+      enabled:'',
+      page_size:10,
+      current:1,
+    },
+    tableData:[],
+    departmanentData:[],
+    roleData:[],
+    total:0,
+    dialogTitle:'',//弹窗标题
+    // 添加编辑弹窗
+    submitForm:{
+      admin_name:'',
+      password:'123456a',
+      real_name:'',
+      position:'',
+      dept_id:'',
+      role_id:'',
+      mobile:'',
+      enabled:1,
+      remark:''
+    },
+    rules:{
+      admin_name:{required:true,message:"登录账号不能为空",trigger:"blur"},
+      password:{required:true,message:"登录密码不能为空",trigger:"blur"},
+      real_name:{required:true,message:"姓名不能为空",trigger:"blur"},
+      dept_id:{required:true,message:"所属部门不能为空",trigger:"change"},
+      role_id:{required:true,message:"角色不能为空",trigger:"change"},
+      mobile:[
+        {required:true,message:"手机号不能为空",trigger:"blur"},
+        {asyncValidator: (rule, value) => {
+          return new Promise((resolve, reject) => {
+            if (value.length !=11) {
+              reject('手机号码必须是11位数字'); 
+            } else {
+              resolve();
+            }
+          });
+        },
+        trigger:"blur"}
+      ],
+      enabled:{required:true,message:"状态不能为空",trigger:"change"}
+    },
+    showAddDia:false,
+    isEdit:false,
+    // 重置密码弹窗
+    resetPsdForm:{
+      admin_id:'',
+      pwd:'',
+      confirm_pwd:''
+    },
+    resetPsdRules:{
+      pwd:{required:true,message:"新密码不能为空",trigger:"blur"},
+      confirm_pwd:{required:true,message:"确认新密码不能为空",trigger:"blur"}
+    },
+    showResetDia:false,
+  })
+//  ------------------------------------method
+  // 获取用户列表
+  const userList=()=>{
+    getuserList(user.searchParams).then(res=>{
+      if(res.code == 200){
+        user.tableData = res.data.list || []
+        user.total = res.data.page.total
+      }
+    })
+  }
+  // 获取部门列表
+  const departmentList=()=>{
+    getDepartmentList().then(res=>{
+      user.departmanentData = res.data.list || []
+    })
+  }
+
+  // 获取角色列表
+  const roleList=()=>{
+    getRoleList().then(res=>{
+      if(res.code == 200){
+        user.roleData = res.data.list || []
+      }
+    })
+  }
+  // 刷新列表
+  const refreshList=()=>{
+    user.searchParams.key_word=''
+    user.searchParams.dept_id=''
+    user.searchParams.enabled=''
+    user.searchParams.current=1
+    userList()
+  }
+  // 去添加部门
+  const goAddDepartment=()=>{
+    sessionStorage.setItem('deptOpenDialog','1')
+    router.push('/system/department')
+  }
+  //切换页码
+  const changePageNo = (pageNo)=>{
+    user.searchParams.current = pageNo
+    userList()
+  }
+  // 切换每页的数量
+  const changePageSize=(pageSize)=>{
+    user.searchParams.page_size = pageSize
+    userList()
+  }
+  // 搜索用户
+  const userSearch=(type)=>{
+    if(type=='deptReset'){
+      user.searchParams.dept_id=''
+    }
+    user.searchParams.current = 1
+    userList()
+  }
+  //点击部门树,筛选用户
+  const userSearchByDept=(item)=>{
+    // console.log(item);
+    user.searchParams.dept_id=item.dept_id
+    userSearch()
+  }
+  // 添加用户
+  const addUser=()=>{
+    user.dialogTitle='添加用户'
+
+    user.showAddDia=true
+    user.isEdit = false
+  }
+  // 编辑用户
+  const editUser=(row)=>{
+    user.dialogTitle='编辑用户'
+    // console.log(row);
+    user.submitForm.admin_id=row.admin_id
+    user.submitForm.admin_name=row.admin_name
+    user.submitForm.real_name=row.real_name
+    user.submitForm.position=row.position
+    user.submitForm.dept_id=row.dept_id
+    user.submitForm.role_id=row.role_id
+    user.submitForm.mobile=row.mobile
+    user.submitForm.enabled=row.enabled
+    user.submitForm.remark=row.remark
+    user.showAddDia=true
+    user.isEdit = true
+  }
+  // 更改用户状态
+  const changeUserStatus=(row)=>{
+    let changeStatus = row.enabled==1?0:1
+    editUserStatus({admin_id:row.admin_id,enabled:changeStatus}).then((res) => {
+      if(res.code==200){
+        row.enabled=changeStatus
+        ElMessage.success(`${row.enabled==1?'启用':'禁用'}成功`)
+        // refreshList()
+      }
+    })
+  }
+  // 重置密码
+  const resetPassword=(row)=>{
+    user.dialogTitle='重置密码'
+    user.showResetDia=true
+    user.resetPsdForm.admin_id=row.admin_id
+  }
+  // 删除用户
+  const deleteUser=(row)=>{
+    ElMessageBox.confirm("删除后不可恢复,确认删除该用户吗?",'提示',{
+      cancelButtonText:'取消',
+      confirmButtonText:'确定',
+      type: 'warning'
+    }).then(res=>{
+      deleteUserApi({admin_id:row.admin_id}).then(res=>{
+        if(res.code==200){
+          ElMessage.success('删除用户成功')
+          refreshList()
+        }
+      })
+    }).catch(()=>{})
+  }
+  // 提交
+  const submit=(type)=>{
+    if(type=='add'){
+      submitFormEl.value.validate((valid)=>{
+        if(valid){
+          if(Array.isArray(user.submitForm.dept_id)){
+            user.submitForm.dept_id=user.submitForm.dept_id[(user.submitForm.dept_id.length-1)]
+          }
+          if(user.submitForm.admin_id){
+            //编辑
+            editUserApi(user.submitForm).then(res=>{
+              if(res.code==200){
+                ElMessage.success(`${user.dialogTitle}成功`)
+                user.showAddDia=false
+                refreshList()
+              }
+            })
+          }else{
+            // 添加
+            let params={...user.submitForm,password:md5(user.submitForm.password)}
+            addUserApi(params).then(res=>{
+              if(res.code==200){
+                ElMessage.success(`${user.dialogTitle}成功`)
+                user.showAddDia=false
+                refreshList()
+              }
+            })
+          }
+        }
+      })
+    }else{
+      // reset
+      resetPsdFormEl.value.validate((valid)=>{
+        if(valid){
+          if(user.resetPsdForm.pwd != user.resetPsdForm.confirm_pwd){
+            ElMessage.warning('新密码两次输入不一致,请核对!')
+            return
+          }
+          // console.log(user.resetPsdForm);
+          let newPassword = md5(user.resetPsdForm.pwd)
+          let params={
+            admin_id:user.resetPsdForm.admin_id,
+            pwd:newPassword,
+            confirm_pwd:newPassword
+          }
+          resetUserPsd(params).then(res=>{
+            if(res.code==200){
+              ElMessage.success(`${user.dialogTitle}成功`)
+              user.showResetDia=false
+            }
+          })
+        }
+      })
+    }
+  }
+  // 重置表单、清除错误信息
+  const closeDia=(type)=>{
+    if(type=='add'){
+      // 添加编辑
+      user.submitForm={
+        admin_name:'',
+        password:'123456a',
+        real_name:'',
+        position:'',
+        dept_id:'',
+        role_id:'',
+        mobile:'',
+        enabled:1,
+        remark:''
+      }
+      setTimeout(()=>{
+        // 清除错误信息
+        submitFormEl.value.clearValidate()
+      },0)
+    }else{
+      // 重设密码
+      user.resetPsdForm={
+        pwd:'',
+        confirm_pwd:''
+      }
+      setTimeout(()=>{
+        // 清除错误信息
+        resetPsdFormEl.value.clearValidate()
+      },0)
+    }
+  }
+  // 自定义类名
+  const customNodeClass =(data)=>{
+    if(data.dept_id==user.searchParams.dept_id){
+      return 'is-selectNode'
+    }else{
+      return ''
+    }
+  }
+  
+  //  ---------------------created
+  // 用户列表
+  userList()
+  // 角色列表
+  roleList()
+  // 部门列表
+  departmentList()
+</script>
+
+<template>
+    <div class="system-user-container">
+      <div class="user-container-left">
+        <!-- 部门列表 -->
+        <div class="department-container">
+          <div class="department-title" @click="userSearch('deptReset')">
+            <img src="@/assets/img/icon/institution.png" style="margin-right: 6px;">
+            <span :style="{'color':user.searchParams.dept_id==''?'var(--themeColor)':'#333333'}">弘则研究</span>
+          </div>
+          <el-scrollbar class="department-contentTree" id="department-contentTree" height="calc(100vh - 248px)">
+            <el-tree :data="user.departmanentData" :props="{label:'dept_name',children:'Children',class: customNodeClass}"
+            node-key="dept_id" default-expand-all	:expand-on-click-node="false" @node-click="userSearchByDept">
+            </el-tree>
+          </el-scrollbar>
+          <div class="user-add-department" @click="goAddDepartment" v-permission="'system:user:addDept'">
+            <el-icon size="20px" color="var(--themeColor)" style="cursor: pointer;"><CirclePlus /></el-icon>
+            <span>添加部门</span> 
+          </div>
+        </div>
+      </div>
+      <div class="user-container-right">
+        <div class="user-container-right-header">
+          <!-- 顶部按钮区域 -->
+          <div class="user-container-header-buttons">
+            <el-button type="primary" @click="addUser" class="header-optios-buttons"  v-permission="'system:user:add'">添加用户</el-button>
+          </div>
+          <!-- 搜索区域 -->
+          <div class="user-container-header-search">
+            <el-input v-model="user.searchParams.key_word" placeholder="姓名/账号/手机号" :prefix-icon="Search" 
+            style="width: 440px;margin-right: 30px;" @input="userSearch"/>
+            <el-select v-model="user.searchParams.enabled" placeholder="用户状态" 
+            clearable @change="userSearch" style="width: 286px">
+              <el-option :label="item.label" :value="item.value" v-for="item in userStatus"></el-option>
+            </el-select>
+          </div>
+        </div>
+        <div class="user-container-right-table">
+          <!-- 表格 -->
+          <el-table :data="user.tableData" border max-height="690px" style="border-radius: 8px;position: sticky;"> 
+            <el-table-column label="姓名" show-overflow-tooltip prop="real_name"></el-table-column>
+            <el-table-column label="手机号" show-overflow-tooltip prop="mobile"></el-table-column>
+            <el-table-column label="角色" show-overflow-tooltip prop="role_name"></el-table-column>
+            <el-table-column label="部门/子部门" show-overflow-tooltip prop="dept_full_name"></el-table-column>
+            <el-table-column label="状态" show-overflow-tooltip prop="enabled">
+              <template #default="scope">
+                <span style="color: #C54322;" v-if="scope.row.enabled==0">禁用</span>
+                <span v-else>启用</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作">
+              <template #default="scope">
+                <div class="table-options">
+                  <span class="table-option-buttons" @click="editUser(scope.row)" v-permission="'system:user:edit'">
+                    编辑
+                  </span>
+                  <span class="table-option-buttons" @click="resetPassword(scope.row)" v-permission="'system:user:resetPsd'">
+                    重置密码
+                  </span>
+                  <span class="table-option-buttons" @click="changeUserStatus(scope.row)" v-permission="'system:user:changeStatus'">
+                    {{scope.row.enabled==1?'禁用':'启用'}}
+                  </span>
+                  <span class="table-option-buttons" style="color:#C54322;" @click="deleteUser(scope.row)" v-permission="'system:user:delete'">
+                    删除
+                  </span>
+                </div>
+              </template>
+            </el-table-column>
+            <template #empty>
+              <div class="table-no-data">
+                <img src="@/assets/img/icon/empty-data.png" />
+                <span>暂无数据</span>
+              </div>
+            </template>
+          </el-table>
+          <m-page :pageSize="user.searchParams.page_size" :page_no="user.searchParams.current" 
+          style="margin-top: 20px;"
+          :total="user.total" @handleCurrentChange="changePageNo" @handleSizeChange="changePageSize" />
+        </div>
+      </div>
+      <!-- 添加/编辑弹窗 -->
+      <el-dialog :title="user.dialogTitle" 
+        v-model="user.showAddDia" 
+        width="500px" 
+        :close-on-click-modal="false" @closed="closeDia('add')">
+          <el-form :model="user.submitForm" label-position="right" 
+          label-width="88px" :rules="user.rules" ref="submitFormEl" style="padding-left: 45px;">
+            <el-form-item label="登录账号" prop="admin_name">
+              <el-input type="text" placeholder="请输入登录账号" v-model="user.submitForm.admin_name" class="inner-dia-input"/>
+            </el-form-item>
+            <el-form-item label="登录密码" v-if="!user.isEdit" prop="password">
+              <el-input type="text" placeholder="请输入登录密码" v-model="user.submitForm.password"  class="inner-dia-input"/>
+            </el-form-item>
+            <el-form-item label="姓名" prop="real_name" >
+              <template #label="scope">
+                <div class="label-custom-equally">
+                  {{scope.label}}
+                </div>
+              </template>
+              <el-input type="text" placeholder="请输入用户姓名" v-model="user.submitForm.real_name" class="inner-dia-input"/>
+            </el-form-item>
+            <el-form-item label="职务" prop="position">
+              <template #label="scope">
+                <div class="label-custom-equally">
+                  {{scope.label}}
+                </div>
+              </template>
+              <el-input type="text" placeholder="请输入职务" v-model="user.submitForm.position" class="inner-dia-input"/>
+            </el-form-item>
+            <el-form-item label="所属部门" prop="dept_id" >
+              <el-cascader :options="user.departmanentData" v-model="user.submitForm.dept_id" style="width:286px"
+              :props="{value:'dept_id',label:'dept_name',children:'Children',checkStrictly: true}" 
+              ></el-cascader>
+            </el-form-item>
+            <el-form-item label="角色" prop="role_id">
+              <template #label="scope">
+                <div class="label-custom-equally">
+                  {{scope.label}}
+                </div>
+              </template>
+              <el-select v-model="user.submitForm.role_id" placeholder="请选择角色" class="inner-dia-input">
+                <el-option :label="item.role_name" :value="item.role_id" v-for="item in user.roleData"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="手机号码" prop="mobile">
+              <el-input type="text" placeholder="请输入手机号码" v-model="user.submitForm.mobile" class="inner-dia-input"/>
+            </el-form-item>
+            <el-form-item label="状态" prop="enabled" >
+              <template #label="scope">
+                <div class="label-custom-equally">
+                  {{scope.label}}
+                </div>
+              </template>
+              <el-radio-group v-model="user.submitForm.enabled">
+                <el-radio :label="1">启用</el-radio>
+                <el-radio :label="0">禁用</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="备注" prop="remark">
+              <template #label="scope">
+                <div class="label-custom-equally">
+                  {{scope.label}}
+                </div>
+              </template>
+              <el-input type="text" placeholder="请输入备注" v-model="user.submitForm.remark" class="inner-dia-input"/>
+            </el-form-item>
+          </el-form>
+          <template #footer>
+            <el-button @click="user.showAddDia=false">取消</el-button>
+            <el-button @click="submit('add')" type="primary">保存</el-button>
+          </template>
+      </el-dialog>
+      <!-- 重置密码弹窗 -->
+      <el-dialog :title="user.dialogTitle" 
+        v-model="user.showResetDia" 
+        width="500px" @closed="closeDia('reset')"
+        :close-on-click-modal="false">
+          <el-form :model="user.resetPsdForm" label-position="right" 
+          label-width="108px" :rules="user.resetPsdRules" ref="resetPsdFormEl">
+            <el-form-item label="新密码" prop="pwd">
+              <el-input type="password" placeholder="输入长度不超过20个字符" v-model="user.resetPsdForm.pwd"
+              maxlength="20" show-password/>
+            </el-form-item>
+            <el-form-item label="确认新密码" prop="confirm_pwd">
+              <el-input type="password" placeholder="输入长度不超过20个字符" v-model="user.resetPsdForm.confirm_pwd"
+              maxlength="20" show-password/>
+            </el-form-item>
+          </el-form>
+          <template #footer>
+            <el-button @click="user.showResetDia=false">取消</el-button>
+            <el-button @click="submit('resetPsd')" type="primary">保存</el-button>
+          </template>
+      </el-dialog>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+  .system-user-container{
+    height: 100%;
+    display: flex;
+    .user-container-left{
+      padding:20px;
+      min-width: 260px;
+      background-color: white;
+      border-radius: 8px;
+      box-sizing: border-box;
+      .department-container{
+        // margin-top: 28px;
+        .department-title{
+          margin-bottom: 16px;
+          display: flex;
+          align-items: center;
+          cursor: pointer;
+          span{
+            font-size: 14px;
+          }
+        }
+        .user-add-department{
+          display:flex;
+          align-items:center;
+          justify-content: center;
+          margin-top: 30px;
+          span{
+            cursor:pointer;
+            margin-left: 6px;
+            color: $themeColor;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+    .user-container-right{
+      margin: 0 30px;
+      flex-grow: 1;
+      overflow: hidden;
+      .user-container-right-header{
+        display: flex;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        .user-container-header-search{
+          display: flex;
+          align-items: center;
+          flex-wrap: wrap;
+        }
+      }
+      .user-container-right-table{
+        margin-top: 30px;
+      }
+    }
+    .inner-dia-input{
+      width: 286px;
+    }
+  }
+</style>
+<style lang="scss">
+#department-contentTree{
+  .el-tree{
+    .el-tree-node__expand-icon.is-leaf{
+      background-image: none;
+    }
+    .is-selectNode{
+      &>.el-tree-node__content{
+        .el-tree-node__label{
+          color: $themeColor;
+        }
+      }
+    }
+  }
+
+}
+
+</style>

+ 134 - 0
vite.config.js

@@ -0,0 +1,134 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from "path"
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import ElementPlus from "unplugin-element-plus/vite"
+import {createSvgIconsPlugin} from 'vite-plugin-svg-icons'
+// import vitePluginImagemin from 'vite-plugin-imagemin'
+
+// Element-plus 图标按需引入和自动引入
+// import Icons from "unplugin-icons/vite"
+// import IconsResolver from "unplugin-icons/resolver"
+
+const pathSrc = path.resolve(__dirname, 'src')
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+    // 自动导入
+    AutoImport({
+      imports:['vue'],
+      resolvers: [
+        ElementPlusResolver(),
+        // IconsResolver({
+        //   prefix: "Icon",
+        // }),
+      ],
+      dts: path.resolve(pathSrc, "auto-imports.d.ts")
+    }),
+    // Element-plus 按需引入
+    Components({
+      resolvers: [
+        ElementPlusResolver(),
+        // IconsResolver({
+        //   enabledCollections: ["ep"],
+        // })
+      ],
+      dts: path.resolve(pathSrc, "components.d.ts"),
+    }),
+    // Icons({
+    //   autoInstall: true,
+    // }),
+    // 样式问题
+    ElementPlus({
+      importStyle: "sass",
+      useSource: true
+    }),
+    // //图片压缩
+    // vitePluginImagemin({
+    //   gifsicle: {
+    //     optimizationLevel: 7,
+    //     interlaced: false
+    //   }
+    // }),
+    createSvgIconsPlugin({
+      // 指定需要缓存的图标文件夹
+      iconDirs: [path.resolve(process.cwd(), 'src/assets/svg-icons')],
+      // 指定symbolId格式
+      symbolId: 'svgIcon-[dir]-[name]',
+      /**
+       * 自定义插入位置
+       * @default: body-last
+       */
+      // inject?: 'body-last' | 'body-first'
+
+      /**
+       * custom dom id
+       * @default: __svg__icons__dom__
+       */
+      // customDomId: '__svg__icons__dom__'
+    })
+  ],
+  resolve:{
+    alias:{
+      "@":path.resolve(__dirname,"./src")
+    }
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: `@use "@/styles/element.scss" as *; @use "@/styles/theme.scss" as *;`
+      }
+    }
+  },
+  server:{
+    host:true
+  },
+  build:{
+    outDir: 'fsms_web',
+    minify:'terser',
+    terserOptions:{
+      compress:{
+        drop_console:true,
+        drop_debugger:true
+      },
+      output:{
+        comments:true
+      }
+    },
+    rollupOptions:{
+      output:{
+        manualChunks(id){
+          if(id.includes('node_modules')){
+            if(id.includes('element-plus')){
+              // element-plus 单独打包
+              return id.toString().split('node_modules/')[1].split('/')[0].toString()
+            }
+            // tinymce富文本编辑器单独打包
+            if(id.includes('tinymce')){
+              // tinymce富文本的themes比较大,单独打包成一个js
+              if(id.toString().split('node_modules/')[1].split('/')[1]=='themes'){ 
+                return id.toString().split('node_modules/')[1].split('/')[0].toString()+'-'+id.toString().split('node_modules/')[1].split('/')[1].toString()
+              }else{
+                return id.toString().split('node_modules/')[1].split('/')[0].toString()
+              }
+              
+            }
+          }
+        },
+        chunkFileNames:'assets/js/[name]-[hash].js',
+        entryFileNames:'assets/js/[name]-[hash].js',
+        assetFileNames:(file)=>{
+          if(file.name.endsWith('.css')){
+            return 'assets/css/[name]-[hash].css'
+          }else{
+            return 'assets/media/[name]-[hash].[ext]'
+          }
+        }
+      }
+    }
+  }
+})

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