Преглед на файлове

放开字典维护菜单

zuy преди 2 години
родител
ревизия
d4162d00b1

+ 0 - 1
admin-web/src/layout/navMenu/vertical.vue

@@ -4,7 +4,6 @@
 		:default-active="state.defaultActive"
 		background-color="transparent"
 		:collapse="state.isCollapse"
-		:unique-opened="getThemeConfig.isUniqueOpened"
 		:collapse-transition="false"
 	>
 		<template v-for="val in menuLists[0].children">

+ 60 - 15
admin-web/src/router/route.ts

@@ -163,23 +163,53 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             icon: 'ele-User',
                         },
                     },
-                    /*         {
-                               path: '/station/stat',
-                               name: 'adminStationStat',
-                               component: () => import('/@/views/admin/station/stat/index.vue'),
-                               meta: {
-                                   title: '营收概览',
-                                   isLink: '',
-                                   isHide: false,
-                                   isKeepAlive: true,
-                                   isAffix: false,
-                                   isIframe: false,
-
-                                   icon: 'ele-Compass',
-                               },
-                           },*/
+                    {
+                        path: '/station/statMonth',
+                        name: 'adminStationStatMonth',
+                        component: () => import('/@/views/admin/station/stat/index.vue'),
+                        meta: {
+                            title: '站点月表',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            perm:"stationStatMonth.list",
+                            icon: 'ele-Histogram',
+                        },
+                    },
+                    {
+                        path: '/station/statement',
+                        name: 'adminStatement',
+                        component: () => import('/@/views/admin/station/statment/index.vue'),
+                        meta: {
+                            title: '对账单',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            perm:"statement.list",
+                            icon: 'ele-CreditCard',
+                        },
+                    },
                 ]
             },
+            {
+                path: '/investor',
+                name: 'adminInvestor',
+                component: () => import('/@/views/admin/investor/index.vue'),
+                meta: {
+                    title: '投资者',
+                    isLink: '',
+                    isHide: false,
+                    isKeepAlive: true,
+                    isAffix: false,
+                    isIframe: false,
+                    icon: 'ele-Avatar',
+                    perm:"investor.list",
+                }
+            },
             {
                 path: '/banner',
                 name: 'adminBanner',
@@ -331,6 +361,21 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             icon: 'ele-Compass',
                         },
                     },
+                    {
+                        path: '/org/dict',
+                        name: 'orgDict',
+                        component: () => import('/@/views/admin/dict/index.vue'),
+                        meta: {
+                            title: '数据字典',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            perm:"dict.list",
+                            icon: 'ele-Collection',
+                        },
+                    },
                 ]
             },
    /*         {

+ 343 - 292
admin-web/src/theme/app.scss

@@ -2,426 +2,477 @@
 ------------------------------- */
 
 * {
-	margin: 0;
-	padding: 0;
-	box-sizing: border-box;
-	outline: none !important;
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  outline: none !important;
 }
 
 :root {
-	--next-color-white: #ffffff;
-	--next-bg-main-color: #f8f8f8;
-	--next-bg-color: #f5f5ff;
-	--next-border-color-light: #f1f2f3;
-	--next-color-primary-lighter: #ecf5ff;
-	--next-color-success-lighter: #f0f9eb;
-	--next-color-warning-lighter: #fdf6ec;
-	--next-color-danger-lighter: #fef0f0;
-	--next-color-dark-hover: #0000001a;
-	--next-color-menu-hover: rgba(0, 0, 0, 0.2);
-	--next-color-user-hover: rgba(0, 0, 0, 0.04);
-	--next-color-seting-main: #e9eef3;
-	--next-color-seting-aside: #d3dce6;
-	--next-color-seting-header: #b3c0d1;
+  --next-color-white: #ffffff;
+  --next-bg-main-color: #f8f8f8;
+  --next-bg-color: #f5f5ff;
+  --next-border-color-light: #f1f2f3;
+  --next-color-primary-lighter: #ecf5ff;
+  --next-color-success-lighter: #f0f9eb;
+  --next-color-warning-lighter: #fdf6ec;
+  --next-color-danger-lighter: #fef0f0;
+  --next-color-dark-hover: #0000001a;
+  --next-color-menu-hover: rgba(0, 0, 0, 0.2);
+  --next-color-user-hover: rgba(0, 0, 0, 0.04);
+  --next-color-seting-main: #e9eef3;
+  --next-color-seting-aside: #d3dce6;
+  --next-color-seting-header: #b3c0d1;
 }
 
 html,
 body,
 #app {
-	margin: 0;
-	padding: 0;
-	width: 100%;
-	height: 100%;
-	font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
-	font-weight: 400;
-	-webkit-font-smoothing: antialiased;
-	-webkit-tap-highlight-color: transparent;
-	background-color: var(--next-bg-main-color);
-	font-size: 14px;
-	overflow: hidden;
-	position: relative;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+  font-weight: 400;
+  -webkit-font-smoothing: antialiased;
+  -webkit-tap-highlight-color: transparent;
+  background-color: var(--next-bg-main-color);
+  font-size: 14px;
+  overflow: hidden;
+  position: relative;
 }
 
 /* 主布局样式
 ------------------------------- */
 .layout-container {
-	width: 100%;
-	height: 100%;
-	.layout-pd {
-		padding: 15px !important;
-	}
-	.layout-flex {
-		display: flex;
-		flex-direction: column;
-		flex: 1;
-	}
-	.layout-aside {
-		background: var(--next-bg-menuBar);
-		box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
-		height: inherit;
-		position: relative;
-		z-index: 1;
-		display: flex;
-		flex-direction: column;
-		overflow-x: hidden !important;
-		.el-scrollbar__view {
-			overflow: hidden;
-		}
-	}
-	.layout-header {
-		padding: 0 !important;
-		height: auto !important;
-	}
-	.layout-main {
-		padding: 0 !important;
-		overflow: hidden;
-		width: 100%;
-		background-color: var(--next-bg-main-color);
-		display: flex;
-		flex-direction: column;
-		// 内层 el-scrollbar样式,用于界面高度自适应(main.vue)
-		.layout-main-scroll {
-			@extend .layout-flex;
-			.layout-parent {
-				@extend .layout-flex;
-				position: relative;
-			}
-		}
-	}
-	// 用于界面高度自适应
-	.layout-padding {
-		@extend .layout-pd;
-		position: absolute;
-		left: 0;
-		top: 0;
-		height: 100%;
-		width: 100%;
-		overflow: hidden;
-		@extend .layout-flex;
-		&-auto {
-			height: inherit;
-			@extend .layout-flex;
-		}
-		&-view {
-			background: var(--el-color-white);
-			width: 100%;
-			height: 100%;
-			border-radius: 4px;
-			border: 1px solid var(--el-border-color-light, #ebeef5);
-			overflow: hidden;
-		}
-	}
-	// 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
-	.layout-padding-unset {
-		padding: 0 !important;
-		&-view {
-			border-radius: 0 !important;
-			border: none !important;
-		}
-	}
-	// 用于设置 iframe loading 时的高度(loading 垂直居中显示)
-	.layout-iframe {
-		.el-loading-parent--relative {
-			height: 100%;
-		}
-	}
-	.el-scrollbar {
-		width: 100%;
-	}
-	.layout-el-aside-br-color {
-		border-right: 1px solid var(--el-border-color-light, #ebeef5);
-	}
-	// pc端左侧导航样式
-	.layout-aside-pc-220 {
-		width: 220px !important;
-		transition: width 0.3s ease;
-	}
-	.layout-aside-pc-64 {
-		width: 64px !important;
-		transition: width 0.3s ease;
-	}
-	.layout-aside-pc-1 {
-		width: 1px !important;
-		transition: width 0.3s ease;
-	}
-	// 手机端左侧导航样式
-	.layout-aside-mobile {
-		position: fixed;
-		top: 0;
-		left: -220px;
-		width: 220px;
-		z-index: 9999999;
-	}
-	.layout-aside-mobile-close {
-		left: -220px;
-		transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
-	}
-	.layout-aside-mobile-open {
-		left: 0;
-		transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
-	}
-	.layout-aside-mobile-mode {
-		position: fixed;
-		top: 0;
-		right: 0;
-		bottom: 0;
-		left: 0;
-		height: 100%;
-		background-color: rgba(0, 0, 0, 0.5);
-		z-index: 9999998;
-		animation: error-img 0.3s;
-	}
-	.layout-mian-height-50 {
-		height: calc(100vh - 50px);
-	}
-	.layout-columns-warp {
-		flex: 1;
-		display: flex;
-		overflow: hidden;
-	}
-	.layout-hide {
-		display: none;
-	}
+  width: 100%;
+  height: 100%;
+
+  .layout-pd {
+    padding: 15px !important;
+  }
+
+  .layout-flex {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+  }
+
+  .layout-aside {
+    background: var(--next-bg-menuBar);
+    box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
+    height: inherit;
+    position: relative;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    overflow-x: hidden !important;
+
+    .el-scrollbar__view {
+      overflow: hidden;
+    }
+  }
+
+  .layout-header {
+    padding: 0 !important;
+    height: auto !important;
+  }
+
+  .layout-main {
+    padding: 0 !important;
+    overflow: hidden;
+    width: 100%;
+    background-color: var(--next-bg-main-color);
+    display: flex;
+    flex-direction: column;
+    // 内层 el-scrollbar样式,用于界面高度自适应(main.vue)
+    .layout-main-scroll {
+      @extend .layout-flex;
+
+      .layout-parent {
+        @extend .layout-flex;
+        position: relative;
+      }
+    }
+  }
+
+  // 用于界面高度自适应
+  .layout-padding {
+    @extend .layout-pd;
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+    @extend .layout-flex;
+
+    &-auto {
+      height: inherit;
+      @extend .layout-flex;
+    }
+
+    &-view {
+      background: var(--el-color-white);
+      width: 100%;
+      height: 100%;
+      border-radius: 4px;
+      border: 1px solid var(--el-border-color-light, #ebeef5);
+      overflow: hidden;
+    }
+  }
+
+  // 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
+  .layout-padding-unset {
+    padding: 0 !important;
+
+    &-view {
+      border-radius: 0 !important;
+      border: none !important;
+    }
+  }
+
+  // 用于设置 iframe loading 时的高度(loading 垂直居中显示)
+  .layout-iframe {
+    .el-loading-parent--relative {
+      height: 100%;
+    }
+  }
+
+  .el-scrollbar {
+    width: 100%;
+  }
+
+  .layout-el-aside-br-color {
+    border-right: 1px solid var(--el-border-color-light, #ebeef5);
+  }
+
+  // pc端左侧导航样式
+  .layout-aside-pc-220 {
+    width: 220px !important;
+    transition: width 0.3s ease;
+  }
+
+  .layout-aside-pc-64 {
+    width: 64px !important;
+    transition: width 0.3s ease;
+  }
+
+  .layout-aside-pc-1 {
+    width: 1px !important;
+    transition: width 0.3s ease;
+  }
+
+  // 手机端左侧导航样式
+  .layout-aside-mobile {
+    position: fixed;
+    top: 0;
+    left: -220px;
+    width: 220px;
+    z-index: 9999999;
+  }
+
+  .layout-aside-mobile-close {
+    left: -220px;
+    transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
+  }
+
+  .layout-aside-mobile-open {
+    left: 0;
+    transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
+  }
+
+  .layout-aside-mobile-mode {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    z-index: 9999998;
+    animation: error-img 0.3s;
+  }
+
+  .layout-mian-height-50 {
+    height: calc(100vh - 50px);
+  }
+
+  .layout-columns-warp {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+  }
+
+  .layout-hide {
+    display: none;
+  }
 }
 
 /* element plus 全局样式
 ------------------------------- */
 .layout-breadcrumb-seting {
-	.el-divider {
-		background-color: rgb(230, 230, 230);
-	}
+  .el-divider {
+    background-color: rgb(230, 230, 230);
+  }
 }
 
 /* nprogress 进度条跟随主题颜色
 ------------------------------- */
 #nprogress {
-	.bar {
-		background: var(--el-color-primary) !important;
-		z-index: 9999999 !important;
-	}
+  .bar {
+    background: var(--el-color-primary) !important;
+    z-index: 9999999 !important;
+  }
 }
 
 /* flex 弹性布局
 ------------------------------- */
 .flex {
-	display: flex;
+  display: flex;
 }
+
 .flex-auto {
-	flex: 1;
-	overflow: hidden;
+  flex: 1;
+  overflow: hidden;
 }
+
 .flex-center {
-	@extend .flex;
-	flex-direction: column;
-	width: 100%;
-	overflow: hidden;
+  @extend .flex;
+  flex-direction: column;
+  width: 100%;
+  overflow: hidden;
 }
+
 .flex-margin {
-	margin: auto;
+  margin: auto;
 }
+
 .flex-warp {
-	display: flex;
-	flex-wrap: wrap;
-	align-content: flex-start;
-	margin: 0 -5px;
-	.flex-warp-item {
-		padding: 5px;
-		.flex-warp-item-box {
-			width: 100%;
-			height: 100%;
-		}
-	}
+  display: flex;
+  flex-wrap: wrap;
+  align-content: flex-start;
+  margin: 0 -5px;
+
+  .flex-warp-item {
+    padding: 5px;
+
+    .flex-warp-item-box {
+      width: 100%;
+      height: 100%;
+    }
+  }
 }
 
-.flex-justify-center{
-	justify-content: center;
+.flex-justify-center {
+  justify-content: center;
 }
 
-.flex-justify-around{
-	justify-content: space-around;
+.flex-justify-around {
+  justify-content: space-around;
 }
-.flex-justify-between{
-	justify-content: space-between;
+
+.flex-justify-between {
+  justify-content: space-between;
 }
-.flex-align-items-center{
-	align-items: center;
-	align-content: center;
+
+.flex-align-items-center {
+  align-items: center;
+  align-content: center;
 }
 
 /* cursor 鼠标形状
 ------------------------------- */
 // 默认
 .cursor-default {
-	cursor: default !important;
+  cursor: default !important;
 }
+
 // 帮助
 .cursor-help {
-	cursor: help !important;
+  cursor: help !important;
 }
+
 // 手指
 .cursor-pointer {
-	cursor: pointer !important;
+  cursor: pointer !important;
 }
+
 // 移动
 .cursor-move {
-	cursor: move !important;
+  cursor: move !important;
 }
 
+// 禁用
+.cursor-disabled {
+  cursor: not-allowed !important;
+}
+
+
 /* 宽高 100%
 ------------------------------- */
 .w100 {
-	width: 100% !important;
+  width: 100% !important;
 }
+
 .h100 {
-	height: 100% !important;
+  height: 100% !important;
 }
+
 .vh100 {
-	height: 100vh !important;
+  height: 100vh !important;
 }
+
 .max100vh {
-	max-height: 100vh !important;
+  max-height: 100vh !important;
 }
+
 .min100vh {
-	min-height: 100vh !important;
+  min-height: 100vh !important;
 }
 
 .wd100 {
-	width: 100px !important;
+  width: 100px !important;
 }
 
 .wd150 {
-	width: 150px !important;
+  width: 150px !important;
 }
 
 .wd200 {
-	width: 200px !important;
+  width: 200px !important;
 }
 
 
 /* 颜色值
 ------------------------------- */
 .color-primary {
-	color: var(--el-color-primary);
+  color: var(--el-color-primary);
 }
+
 .color-success {
-	color: var(--el-color-success);
+  color: var(--el-color-success);
 }
+
 .color-warning {
-	color: var(--el-color-warning);
+  color: var(--el-color-warning);
 }
+
 .color-danger {
-	color: var(--el-color-danger);
+  color: var(--el-color-danger);
 }
+
 .color-info {
-	color: var(--el-color-info);
+  color: var(--el-color-info);
 }
 
 //固定宽度
 @for $i from 0 through 30 {
-	.wd#{$i*5} {
-		width: #{$i*5}px !important;
-	}
+  .wd#{$i*5} {
+    width: #{$i*5}px !important;
+  }
 }
 
 /* 字体大小全局样式
 ------------------------------- */
 @for $i from 10 through 32 {
-	.font#{$i} {
-		font-size: #{$i}px !important;
-	}
+  .font#{$i} {
+    font-size: #{$i}px !important;
+  }
 }
 
 /* 外边距、内边距全局样式
 ------------------------------- */
 @for $i from 1 through 35 {
-	.mt#{$i} {
-		margin-top: #{$i}px !important;
-	}
-	.mr#{$i} {
-		margin-right: #{$i}px !important;
-	}
-	.mb#{$i} {
-		margin-bottom: #{$i}px !important;
-	}
-	.ml#{$i} {
-		margin-left: #{$i}px !important;
-	}
-	.pt#{$i} {
-		padding-top: #{$i}px !important;
-	}
-	.pr#{$i} {
-		padding-right: #{$i}px !important;
-	}
-	.pb#{$i} {
-		padding-bottom: #{$i}px !important;
-	}
-	.pl#{$i} {
-		padding-left: #{$i}px !important;
-	}
-	.pv#{$i}{
-		padding: #{$i}px auto !important;
-	}
+  .mt#{$i} {
+    margin-top: #{$i}px !important;
+  }
+  .mr#{$i} {
+    margin-right: #{$i}px !important;
+  }
+  .mb#{$i} {
+    margin-bottom: #{$i}px !important;
+  }
+  .ml#{$i} {
+    margin-left: #{$i}px !important;
+  }
+  .pt#{$i} {
+    padding-top: #{$i}px !important;
+  }
+  .pr#{$i} {
+    padding-right: #{$i}px !important;
+  }
+  .pb#{$i} {
+    padding-bottom: #{$i}px !important;
+  }
+  .pl#{$i} {
+    padding-left: #{$i}px !important;
+  }
+  .pv#{$i} {
+    padding: #{$i}px auto !important;
+  }
 
-	.ph#{$i}{
-		padding: auto #{$i}px  !important;
-	}
+  .ph#{$i} {
+    padding: auto #{$i}px !important;
+  }
 
-	.pd#{$i}{
-		padding: #{$i}px  !important;
-	}
+  .pd#{$i} {
+    padding: #{$i}px !important;
+  }
 
-	.mv#{$i}{
-		margin: #{$i}px auto !important;
-	}
+  .mv#{$i} {
+    margin: #{$i}px auto !important;
+  }
 
-	.mh#{$i}{
-		margin: auto #{$i}px  !important;
-	}
+  .mh#{$i} {
+    margin: auto #{$i}px !important;
+  }
 }
 
 
-.hc{
-	visibility: hidden !important;
-	transition: all 0.5s;
+.hc {
+  visibility: hidden !important;
+  transition: all 0.5s;
 }
 
-.hp{
-	&:hover{
-		.hc{
-			visibility: visible !important;
-		}
-	}
+.hp {
+  &:hover {
+    .hc {
+      visibility: visible !important;
+    }
+  }
 }
 
-.text-align-left{
-	text-align: left;
+.text-align-left {
+  text-align: left;
 }
 
-.text-align-center{
-	text-align: center;
+.text-align-center {
+  text-align: center;
 }
-.text-align-right{
-	text-align: right;
+
+.text-align-right {
+  text-align: right;
 }
 
 
-.float-right{
-	float: right;
+.float-right {
+  float: right;
 }
 
-.float-left{
-	float: left;
+.float-left {
+  float: left;
 }
 
-.code{
-	width: 100%;
-	padding: 5px;
-	margin: 5px;
-	border-radius: 5px;
-	background-color: #f5f7fa;
-	color:#000;
+.code {
+  width: 100%;
+  padding: 5px;
+  margin: 5px;
+  border-radius: 5px;
+  background-color: #f5f7fa;
+  color: #000;
 }
 
-.scroll_y{
-	overflow-y: scroll;
+.scroll_y {
+  overflow-y: scroll;
 }

+ 309 - 0
admin-web/src/views/admin/dict/index.vue

@@ -0,0 +1,309 @@
+<template>
+  <div class="system-container layout-padding">
+    <el-card shadow="hover" class="layout-padding-auto">
+      <el-form
+          :model="state.formQuery"
+          ref="queryRef"
+          size="default" label-width="0px" class="mt5 mb5">
+        <!--        <el-input
+                    v-model="state.formQuery.name"
+                    placeholder="字典名称"
+                    clearable
+                    @blur="loadData(true)"
+                    class="wd150 mr10">
+                </el-input>-->
+
+        <el-input
+            v-model="state.formQuery.code"
+            placeholder="字典编码"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+
+        <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+
+        <el-button class="ml10" plain size="default" type="primary" @click="handleAddDict" v-auth="'dict.add'">
+          <SvgIcon name="ele-FolderAdd"/>
+          创建
+        </el-button>
+      </el-form>
+
+      <div class="flex">
+        <el-table
+            style="width: 400px;"
+            width="400"
+            border
+            stripe="stripe"
+            :height="state.tableData.height"
+            highlight-current-row
+            current-row-key="id"
+            row-key="id"
+            :data="state.tableData.data"
+            @row-click="handleDictRowClick"
+            v-loading="state.tableData.loading">
+          <template #empty>
+            <el-empty></el-empty>
+          </template>
+          <el-table-column
+              v-for="field in state.columns"
+              :key="field.prop"
+              :label="field.label"
+              :column-key="field.prop"
+              :width="field.width"
+              :min-width="field.minWidth"
+              :fixed="field.fixed"
+              :sortable="field.sortable"
+              :show-overflow-tooltip="!field.fixed&&field.width>150"
+          >
+            <template #default="{row}">
+              <template v-if="field.prop==='expand'">
+                <p style="padding-left: 2em;" v-html="row[field.prop]"></p>
+              </template>
+              <template v-else>
+                <div>{{ row[field.prop] }}</div>
+              </template>
+
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="dict-content__box flex-auto ml10 pr5 pl5" v-if="state.visible">
+          <el-input
+              :readonly="state.dictForm.id"
+              v-model="state.dictForm.code"
+              placeholder="字典编码"
+              clearable
+              size="default"
+              class="w100 mr10">
+          </el-input>
+
+          <el-input
+              v-model="state.dictForm.remark"
+              placeholder="备注"
+              clearable
+              type="textarea"
+              size="default"
+              :rows="2"
+              class="w100  mt10">
+          </el-input>
+
+          <el-row :gutter="10" class="mt20">
+            <el-col :span="8">字典项名称</el-col>
+            <el-col :span="8">字典项码值</el-col>
+            <el-col :span="4">排序权重</el-col>
+          </el-row>
+          <div v-for="(item,idx) in state.dictForm.list" :key="idx" class="mt10">
+            <el-row :gutter="10">
+              <el-col :span="8">
+                <el-input size="default" v-model="item.name" placeholder="字典名称"></el-input>
+              </el-col>
+              <el-col :span="8">
+                <el-input size="default" :readonly="item.id" v-model="item.value" placeholder="字典码值"></el-input>
+              </el-col>
+              <el-col :span="8">
+                <el-input-number :min="1"  size="default" controls-position="right" v-model="item.weight" placeholder="排序权重"></el-input-number>
+                <SvgIcon name="ele-RemoveFilled" color="var(--el-color-danger)" class="ml3 cursor-pointer" @click="handleDeleteDictItem(idx)"></SvgIcon>
+              </el-col>
+            </el-row>
+          </div>
+
+          <el-button  v-auth="'dict.add'" size="small" class="mt10 mr5" type="primary" @click="handleAddDictItem">新增</el-button>
+          <el-button v-auth="'dict.modify'" size="small" class="mt10" type="success" @click="handleSaveDict">保存</el-button>
+
+        </div>
+      </div>
+
+
+      <el-affix position="bottom" :offset="49">
+        <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
+      </el-affix>
+
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts" name="Dict">
+import {defineAsyncComponent, reactive, onMounted, ref, getCurrentInstance, nextTick} from 'vue';
+import ExtPage from "/@/components/form/ExtPage.vue"
+import {$body} from "/@/utils/request";
+import u from "/@/utils/u";
+import {Msg} from "/@/utils/message";
+// 引入组件
+
+const {proxy}: any = getCurrentInstance();
+import {ElButton} from 'element-plus'
+
+// 定义变量内容
+const queryRef = ref();
+const dicDialogRef = ref();
+
+const state = reactive({
+  tableData: {
+    data: [] as Array<any>,
+    total: 0,
+    loading: false,
+    height: 500
+  },
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 20,
+    total: 0
+  },
+  formQuery: {},
+  columns: [
+    {label: '编码', prop: 'code', query: true, type: 'text', resizable: true, width: 250, fixed: 'left'},
+    {label: '备注', prop: 'remark', query: true, type: 'text', resizable: true, width: 160},
+    {
+      label: '操作时间', prop: 'updateTime', width: 180, query: true, type: 'datetime', resizable: true,
+    }
+  ],
+  dictForm: {
+    visible:false,
+    id: 0,
+    code: '',
+    remark: '',
+    list: [] as Array<any>
+  },
+  dictLoading: false,
+  visible:false
+});
+
+
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $body('dataDict/listV2', {...state.formQuery, ...state.pageQuery}).then((res: any) => {
+    let {list, total} = res;
+    state.tableData.data = list;
+    state.pageQuery.total = total;
+    state.tableData.loading = false;
+  })
+};
+/*// 打开新增字典弹窗
+const onOpenAddDic = (type: string) => {
+  console.log(type)
+  dicDialogRef.value.openDialog(type);
+};
+// 打开修改字典弹窗
+const onOpenEditDic = (type: string, row: RowDicType) => {
+  console.log(row)
+  dicDialogRef.value.openDialog(type, row);
+};*/
+// 删除字典
+const onRowDel = (row: RowDicType) => {
+  console.log(row)
+  Msg.confirm(`此操作将永久删除字典名称:“${row.type}”,是否继续?`).then(() => {
+    loadData();
+    Msg.message("删除成功")
+  })
+}
+
+// 页面加载时
+onMounted(() => {
+  loadData();
+
+  nextTick(() => {
+    let bodyHeight = document.body.clientHeight;
+    let queryHeight = queryRef.value.$el.clientHeight;
+    state.tableData.height = bodyHeight - queryHeight - 220
+  })
+});
+
+const handleDictRowClick = (row: any) => {
+  state.dictLoading = true;
+  Msg.showLoading()
+  $body(`dataDict/list`, {query: {code: row.code}}).then(res => {
+    state.visible = true;
+    Msg.hideLoading()
+    state.dictLoading = false;
+    let {list} = res;
+    if (u.isEmptyOrNull(list)) {
+      state.dictForm = {
+        code: '',
+        remark: '',
+        list: []
+      }
+      return;
+    }
+
+    u.sort(list, 'weight');
+    state.dictForm = {
+      id: list[0].id,
+      code: list[0].code,
+      remark: list[0].remark,
+      list: list
+    }
+
+  })
+}
+
+const handleDeleteDictItem = (idx) => {
+  state.dictForm.list.splice(idx,1)
+}
+
+const handleAddDict = () => {
+  state.visible = true;
+  state.dictForm = {
+    code: '字典码值',
+    remark: '备注',
+    list: [
+      {name: '', value: '', weight: 0}
+    ]
+  }
+}
+
+const handleAddDictItem = () => {
+  state.dictForm.list.push({
+    code: '',
+    name: '',
+    value: '',
+    remark: '',
+    weight: 0
+  })
+}
+
+const handleSaveDict = () => {
+  let params = state.dictForm.list.map(k => {
+    let {id, name, value, weight} = k;
+    return {
+      id, name, value, weight,
+      code: state.dictForm.code,
+      remark: state.dictForm.remark
+    }
+  })
+  $body(`dataDict/saveOrUpdate`, params).then(() => {
+    Msg.message("保存成功")
+  })
+}
+</script>
+
+<style scoped lang="scss">
+.system-container {
+  :deep(.el-card__body) {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    overflow: auto;
+
+    .el-table {
+      flex: 1;
+    }
+  }
+}
+
+.page-content {
+  margin-bottom: 20px;
+}
+
+.page-pager {
+  background-color: #fff;
+  height: 24px;
+}
+</style>

+ 185 - 0
admin-web/src/views/admin/investor/dialog.vue

@@ -0,0 +1,185 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+  <div class="system-dialog-container">
+    <el-drawer
+        :title="state.dialog.title"
+        v-model="state.dialog.isShowDialog"
+        width="820px"
+        append-to-body
+        destroy-on-close
+        :close-on-click-modal="false"
+    >
+      <el-form
+          :model="state.form"
+          :rules="rules"
+          label-position="left"
+          ref="formRef"
+          size="default"
+          label-width="100px"
+          class="mt5">
+        <el-input
+            v-model="state.formQuery.adminUserId"
+            placeholder="客户用户id"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.adminUserName"
+            placeholder="客户姓名"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.stationId"
+            placeholder="站点id"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.vatRate"
+            placeholder="增值税率 0.06表示6%"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.splittingProportion"
+            placeholder="分成比例 0.45表示45%"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.accountName"
+            placeholder="账户名"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.telephone"
+            placeholder="电话号码"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.bankName"
+            placeholder="开户行名称"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.bankCardNo"
+            placeholder="银行卡号"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.status"
+            placeholder="状态:0-无效,1-有效"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.remark"
+            placeholder="备注"
+            clearable
+            class="wd150">
+        </el-input>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="onCancel" size="default">取 消</el-button>
+          <el-button :loading="state.btnLoading" type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup lang="ts" name="InvestorInfoDialog">
+import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
+import {Msg} from "/@/utils/message";
+import {$body, $get} from "/@/utils/request";
+import u from '/@/utils/u'
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = ()=>({
+  ruleForm: {
+    id:0
+  },
+  btnLoading: false,
+  dialog: {
+    isShowDialog: false,
+    type: '',
+    title: '',
+    submitTxt: '',
+  },
+  rules: {},
+})
+
+// 定义变量内容
+const state = reactive(initState());
+
+
+// 打开弹窗
+const open = (action: string='add', row: any) => {
+  state.dialog.title = u.dialog.actions[action].title +"『投资者-物业信息表』"
+  state.dialog.submitTxt = u.dialog.actions[action].btn +"『投资者-物业信息表』"
+  state.dialog.isShowDialog = true;
+  if (action !=='add') {
+    loadData(row.id);
+  }
+};
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.isShowDialog = false;
+  Object.assign(state,initState())
+};
+// 取消
+const onCancel = () => {
+  onClose();
+};
+// 提交
+const onSubmit = () => {
+  formRef.value.validate((valid, fields) => {
+    // console.log('basic checkForm!', valid,fields)
+    if (valid) {
+      state.btnLoading = true;
+      const url = state.ruleForm.id > 0 ? "investorInfo/modify" : "investorInfo/add"
+      $body(url, state.ruleForm).then(() => {
+        state.btnLoading = false;
+        Msg.message('操作成功');
+        console.log('submit!')
+        onClose();
+        emit('refresh');
+      })
+    } else {
+      state.btnLoading = false;
+      Msg.message('表单校验失败', 'error');
+    }
+  })
+
+};
+
+const handleFormChange = (formData: any) => {
+  console.log(formData)
+}
+
+// 初始化表格数据
+const loadData = (id: any) => {
+  $get(`investorInfo/detail/${id}`).then((res: any) => {
+    state.ruleForm = res;
+  })
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

+ 247 - 0
admin-web/src/views/admin/investor/index.vue

@@ -0,0 +1,247 @@
+<style scoped lang="scss">
+.system-container {
+
+  :deep(.el-card__body) {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    flex: 1;
+    overflow: auto;
+
+    .el-table {
+      flex: 1;
+    }
+
+  }
+}
+
+.page-content {
+  margin-bottom: 20px;
+}
+
+.page-pager {
+  background-color: #fff;
+  height: 24px;
+}
+</style>
+<template>
+  <div class="system-container layout-padding">
+    <el-card shadow="hover" class="layout-padding-auto">
+
+
+      <el-form
+          :model="state.formQuery"
+          ref="queryRef"
+          size="default" label-width="0px" class="mt5 mb5">
+        <el-input
+            v-model="state.formQuery.username"
+            placeholder="客户姓名"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.stationId"
+            placeholder="站点id"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.status"
+            placeholder="状态:0-无效,1-有效"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.mobilePhone"
+            placeholder="电话号码"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+
+
+        <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+      </el-form>
+
+      <el-table
+          border
+          stripe="stripe"
+          :height="state.tableData.height"
+          highlight-current-row
+          current-row-key="id"
+          row-key="id"
+          :data="state.tableData.data"
+          v-loading="state.tableData.loading"
+          @selection-change="handleTableSelectionChange"
+          @sort-change="handleTableSortChange">
+        <template #empty>
+          <el-empty></el-empty>
+        </template>
+        <el-table-column type="selection" align="center" width="55" fixed="left"/>
+        <el-table-column
+            v-for="field in state.tableData.columns"
+            :key="field.prop"
+            :label="field.label"
+            :column-key="field.prop"
+            :width="field.width"
+            :min-width="field.minWidth"
+            :fixed="field.fixed"
+            :sortable="field.sortable"
+            :show-overflow-tooltip="!field.fixed&&field.width>150"
+        >
+          <template #default="{row}">
+            <template v-if="field.prop==='expand'">
+              <p style="padding-left: 2em;" v-html="row[field.prop]"></p>
+            </template>
+            <template v-else>
+              <div>{{ row[field.prop] }}</div>
+            </template>
+
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
+    </el-card>
+  </div>
+  <InvestorInfoDialog ref="investorInfoDialogRef" @refresh="loadData(true)"/>
+</template>
+
+<script setup lang="ts" name="InvestorInfoList">
+import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
+import {$body, $get} from "/@/utils/request";
+import {Msg} from "/@/utils/message";
+
+
+import ExtPage from '/@/components/form/ExtPage.vue'
+
+import mittBus from '/@/utils/mitt';
+
+const InvestorInfoDialog = defineAsyncComponent(() => import("/@/views/admin/investor/dialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const investorInfoDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array<any>,
+    loading: false,
+    columns: [
+      {type: 'selection', width: 60, align: 'center', fixed: 'left'},
+      {label: '账户名', prop: 'accountName', resizable: true},
+      {label: '客户姓名', prop: 'adminUserName', resizable: true},
+      {label: '银行卡号', prop: 'bankCardNo', resizable: true},
+      {label: '开户行名称', prop: 'bankName', resizable: true},
+      {label: '创建时间', prop: 'createTime', sortable: 'custom', resizable: true, width: 150},
+      {label: '备注', prop: 'remark', resizable: true},
+      {label: '站点id', prop: 'stationId', resizable: true},
+      {label: '状态:0-无效,1-有效', prop: 'status', sortable: 'custom', align: 'center'},
+      {label: '电话号码', prop: 'telephone', resizable: true},
+      {label: '更新时间', prop: 'updateTime', sortable: 'custom', resizable: true, width: 150},
+      {
+        label: '操作', prop: 'action', width: 180, align: 'center', fixed: 'right',
+      }
+    ],
+  },
+})
+
+
+// 监听双向绑定 modelValue 的变化
+// watch(
+//         () => state.pageIndex,
+//         () => {
+//
+//         }
+// );
+
+//生命周期钩子
+onBeforeMount(() => {
+})
+
+onMounted(() => {
+  loadData();
+
+  nextTick(() => {
+    let bodyHeight = document.body.clientHeight;
+    let queryHeight = queryRef.value.$el.clientHeight;
+    state.tableData.height = bodyHeight - queryHeight - 220
+  })
+
+  mittBus.on("investorInfo.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("investorInfo.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $get(`/investorInfo/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
+    let {list, total} = res;
+    state.tableData.data = list;
+    state.pageQuery.total = total;
+    state.tableData.loading = false;
+  }).catch(e => {
+    console.error(e)
+    state.tableData.loading = false;
+  })
+};
+
+// 打开修改投资者弹窗
+const onRowClick = (type: string, row: any) => {
+  investorInfoDialogRef.value.open(type, row);
+};
+
+// 删除投资者
+const onRowDel = (row: any) => {
+  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
+    $get(`/investorInfo/delete/${row.id}`).then(() => {
+      Msg.message("删除成功", 'success')
+    }).catch(() => {
+      Msg.message("删除失败", 'error')
+    })
+  });
+};
+
+const handleTableSelectionChange = (selection: any) => {
+  console.log("handleTableSelectionChange>>", selection)
+  // emit("on-check-change", selection)
+}
+
+const handleTableSortChange = (column, prop, order) => {
+  console.log("handleTableSortChange>>", column, prop, order)
+  // emit("on-sort-change", column)
+}
+
+
+//endregion
+
+
+// 暴露变量
+// defineExpose({
+//     loadData,
+// });
+</script>

+ 0 - 5
admin-web/src/views/admin/station/list/index.vue

@@ -52,10 +52,6 @@
           新增
         </el-button>
 
-        <el-button  v-auth="'station.add'"   size="default" plain  type="success" class="ml10" @click="handleSettleShow">
-          <SvgIcon name="ele-FolderAdd"/>
-          对账单
-        </el-button>
       </el-form>
 
 <!--      <el-card class="w100">
@@ -138,7 +134,6 @@ import {useRouter} from "vue-router";
 import ExtImage from "/@/components/form/ExtImage.vue";
 const router = useRouter();
 const StationDialog = defineAsyncComponent(() => import("/@/views/admin/station/list/dialog.vue"));
-const SettleDialog = defineAsyncComponent(() => import("/@/views/admin/station/list/settle.vue"));
 
 //定义引用
 const queryRef = ref();

+ 215 - 0
admin-web/src/views/admin/station/stat/dialog.vue

@@ -0,0 +1,215 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+  <div class="system-dialog-container">
+    <el-drawer
+        :title="state.dialog.title"
+        v-model="state.dialog.isShowDialog"
+        width="820px"
+        append-to-body
+        destroy-on-close
+        :close-on-click-modal="false"
+    >
+      <el-form
+          :model="state.form"
+          :rules="rules"
+          label-position="left"
+          ref="formRef"
+          size="default"
+          label-width="100px"
+          class="mt5">
+        <el-input
+            v-model="state.formQuery.stationId"
+            placeholder="站点id"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.statMonth"
+            placeholder="统计时间"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.chargeUsers"
+            placeholder="充电人数"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.validOrders"
+            placeholder="充电有效订单数"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalPower"
+            placeholder="总电量"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalMoney"
+            placeholder="总充电费用"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.elecMoney"
+            placeholder="总电费"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoney"
+            placeholder="总服务费"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoneyDiscount"
+            placeholder="服务费优惠金额"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.discountAmount"
+            placeholder="总优惠金额"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderElec"
+            placeholder="订单平均充电量"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderMoney"
+            placeholder="订单平均充电费用"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgConnectorElec"
+            placeholder="单枪平均日充电量"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.connectorUsageRate"
+            placeholder="设备使用率"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.actualPower"
+            placeholder="实际抄表电量"
+            clearable
+            class="wd150">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.actualElecMoney"
+            placeholder="实际抄表电费金额(分)"
+            clearable
+            class="wd150">
+        </el-input>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="onCancel" size="default">取 消</el-button>
+          <el-button :loading="state.btnLoading" type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup lang="ts" name="StationStatMonthDialog">
+import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
+import {Msg} from "/@/utils/message";
+import {$body, $get} from "/@/utils/request";
+import u from '/@/utils/u'
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = ()=>({
+  ruleForm: {
+    id:0
+  },
+  btnLoading: false,
+  dialog: {
+    isShowDialog: false,
+    type: '',
+    title: '',
+    submitTxt: '',
+  },
+  rules: {},
+})
+
+// 定义变量内容
+const state = reactive(initState());
+
+
+// 打开弹窗
+const open = (action: string='add', row: any) => {
+  state.dialog.title = u.dialog.actions[action].title +"『站点统计表-月』"
+  state.dialog.submitTxt = u.dialog.actions[action].btn +"『站点统计表-月』"
+  state.dialog.isShowDialog = true;
+  if (action !=='add') {
+    loadData(row.id);
+  }
+};
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.isShowDialog = false;
+  Object.assign(state,initState())
+};
+// 取消
+const onCancel = () => {
+  onClose();
+};
+// 提交
+const onSubmit = () => {
+  formRef.value.validate((valid, fields) => {
+    // console.log('basic checkForm!', valid,fields)
+    if (valid) {
+      state.btnLoading = true;
+      const url = state.ruleForm.id > 0 ? "stationStatMonth/modify" : "stationStatMonth/add"
+      $body(url, state.ruleForm).then(() => {
+        state.btnLoading = false;
+        Msg.message('操作成功');
+        console.log('submit!')
+        onClose();
+        emit('refresh');
+      })
+    } else {
+      state.btnLoading = false;
+      Msg.message('表单校验失败', 'error');
+    }
+  })
+
+};
+
+const handleFormChange = (formData: any) => {
+  console.log(formData)
+}
+
+// 初始化表格数据
+const loadData = (id: any) => {
+  $get(`stationStatMonth/detail/${id}`).then((res: any) => {
+    state.ruleForm = res;
+  })
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

+ 333 - 6
admin-web/src/views/admin/station/stat/index.vue

@@ -1,13 +1,340 @@
+<style scoped lang="scss">
+.system-container {
+
+  :deep(.el-card__body) {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    flex: 1;
+    overflow: auto;
+
+    .el-table {
+      flex: 1;
+    }
+
+  }
+}
+
+.page-content {
+  margin-bottom: 20px;
+}
+
+.page-pager {
+  background-color: #fff;
+  height: 24px;
+}
+</style>
 <template>
+  <div class="system-container layout-padding">
+    <el-card shadow="hover" class="layout-padding-auto">
+
 
+      <el-form
+          :model="state.formQuery"
+          ref="queryRef"
+          size="default" label-width="0px" class="mt5 mb5">
+        <el-input
+            v-model="state.formQuery.actualElecMoney"
+            placeholder="实际抄表电费金额(分)"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.actualPower"
+            placeholder="实际抄表电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgConnectorElec"
+            placeholder="单枪平均日充电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderElec"
+            placeholder="订单平均充电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderMoney"
+            placeholder="订单平均充电费用"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.chargeUsers"
+            placeholder="充电人数"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.connectorUsageRate"
+            placeholder="设备使用率"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.discountAmount"
+            placeholder="总优惠金额"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.elecMoney"
+            placeholder="总电费"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoney"
+            placeholder="总服务费"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoneyDiscount"
+            placeholder="服务费优惠金额"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.statMonth"
+            placeholder="统计时间"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.stationId"
+            placeholder="站点id"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalMoney"
+            placeholder="总充电费用"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalPower"
+            placeholder="总电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.updateTime"
+            placeholder=""
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.validOrders"
+            placeholder="充电有效订单数"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+
+
+        <el-button class="ml10"  plain size="default" type="success" @click="loadData(true)">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+      </el-form>
+
+      <el-table
+          border
+          stripe="stripe"
+          :height="state.tableData.height"
+          highlight-current-row
+          current-row-key="id"
+          row-key="id"
+          :data="state.tableData.data"
+          v-loading="state.tableData.loading"
+          @selection-change="handleTableSelectionChange"
+          @sort-change="handleTableSortChange">
+        <template #empty>
+          <el-empty></el-empty>
+        </template>
+        <el-table-column type="selection" align="center" width="55" fixed="left"/>
+        <el-table-column
+            v-for="field in state.tableData.columns"
+            :key="field.prop"
+            :label="field.label"
+            :column-key="field.prop"
+            :width="field.width"
+            :min-width="field.minWidth"
+            :fixed="field.fixed"
+            :sortable="field.sortable"
+            :show-overflow-tooltip="!field.fixed&&field.width>150"
+        >
+          <template #default="{row}">
+            <template v-if="field.prop==='expand'">
+              <p style="padding-left: 2em;" v-html="row[field.prop]"></p>
+            </template>
+            <template v-else>
+              <div>{{row[field.prop]}}</div>
+            </template>
+
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
+    </el-card>
+  </div>
+  <StationStatMonthDialog ref="stationStatMonthDialogRef" @refresh="loadData(true)"/>
 </template>
 
-<script>
-export default {
-  name: "index"
+<script setup lang="ts" name="StationStatMonthList">
+import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
+import {$body,$get} from "/@/utils/request";
+import {Msg} from "/@/utils/message";
+
+
+import ExtPage from '/@/components/form/ExtPage.vue'
+
+import mittBus from '/@/utils/mitt';
+
+const StationStatMonthDialog = defineAsyncComponent(() => import("/@/views/admin/station/stat/dialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const stationStatMonthDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array < any >,
+    loading: false,
+    columns: [
+      {type: 'selection', width: 60, align: 'center', fixed: 'left'},
+      {label: '订单平均充电费用', prop: 'avgOrderMoney', resizable: true,width:130},
+      {label: '充电人数', prop: 'chargeUsers', resizable: true,width:130},
+      {label: '', prop: 'createTime', sortable: 'custom', resizable: true,width:150},
+      {label: '总优惠金额', prop: 'discountAmount', resizable: true,width:130},
+      {label: '总电费', prop: 'elecMoney', resizable: true,width:130},
+      {label: '总服务费', prop: 'serviceMoney', resizable: true,width:130},
+      {label: '服务费优惠金额', prop: 'serviceMoneyDiscount', resizable: true,width:130},
+      {label: '统计时间', prop: 'statMonth', resizable: true},
+      {label: '站点id', prop: 'stationId', resizable: true},
+      {label: '总充电费用', prop: 'totalMoney', resizable: true,width:130},
+      {label: '', prop: 'updateTime', sortable: 'custom', resizable: true,width:150},
+      {label: '充电有效订单数', prop: 'validOrders', resizable: true,width:130},
+      {
+        label: '操作', prop: 'action', width: 180, align: 'center', fixed: 'right',
+      }
+    ],
+  },
+})
+
+
+// 监听双向绑定 modelValue 的变化
+// watch(
+//         () => state.pageIndex,
+//         () => {
+//
+//         }
+// );
+
+//生命周期钩子
+onBeforeMount(() => {
+})
+
+onMounted(() => {
+  loadData();
+
+  nextTick(() => {
+    let bodyHeight = document.body.clientHeight;
+    let queryHeight = queryRef.value.$el.clientHeight;
+    state.tableData.height = bodyHeight - queryHeight - 220
+  })
+
+  mittBus.on("stationStatMonth.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("stationStatMonth.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $body(`/stationStatMonth/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
+    let {list, total} = res;
+    state.tableData.data = list;
+    state.pageQuery.total = total;
+    state.tableData.loading = false;
+  }).catch(e => {
+    console.error(e)
+    state.tableData.loading = false;
+  })
+};
+
+// 打开修改站点统计表-月弹窗
+const onRowClick = (type: string, row: any) => {
+  stationStatMonthDialogRef.value.open(type, row);
+};
+
+// 删除站点统计表-月
+const onRowDel = (row: any) => {
+  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
+    $get(`/stationStatMonth/delete/${row.id}`).then(() => {
+      Msg.message("删除成功", 'success')
+    }).catch(() => {
+      Msg.message("删除失败", 'error')
+    })
+  });
+};
+
+const handleTableSelectionChange = (selection: any) => {
+  console.log("handleTableSelectionChange>>", selection)
+  // emit("on-check-change", selection)
 }
-</script>
 
-<style scoped>
+const handleTableSortChange = (column, prop, order) => {
+  console.log("handleTableSortChange>>", column, prop, order)
+  // emit("on-sort-change", column)
+}
+
+
+//endregion
+
 
-</style>
+// 暴露变量
+// defineExpose({
+//     loadData,
+// });
+</script>

+ 0 - 0
admin-web/src/views/admin/station/list/settle.vue → admin-web/src/views/admin/station/statment/dialog.vue


+ 319 - 0
admin-web/src/views/admin/station/statment/index.vue

@@ -0,0 +1,319 @@
+<style scoped lang="scss">
+.system-container {
+
+  :deep(.el-card__body) {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    flex: 1;
+    overflow: auto;
+
+    .el-table {
+      flex: 1;
+    }
+
+  }
+}
+
+.page-content {
+  margin-bottom: 20px;
+}
+
+.page-pager {
+  background-color: #fff;
+  height: 24px;
+}
+</style>
+<template>
+  <div class="system-container layout-padding">
+    <el-card shadow="hover" class="layout-padding-auto">
+
+
+      <el-form
+          :model="state.formQuery"
+          ref="queryRef"
+          size="default" label-width="0px" class="mt5 mb5">
+        <el-input
+            v-model="state.formQuery.avgConnectorElec"
+            placeholder="单枪平均日充电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderElec"
+            placeholder="订单平均充电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.avgOrderMoney"
+            placeholder="订单平均充电费用"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.chargeUsers"
+            placeholder="充电人数"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.discountAmount"
+            placeholder="总优惠金额"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.elecMoney"
+            placeholder="总电费"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoney"
+            placeholder="总服务费"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.serviceMoneyDiscount"
+            placeholder="服务费优惠金额"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.statMonth"
+            placeholder="统计时间"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.stationId"
+            placeholder="站点id"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalMoney"
+            placeholder="总充电费用"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.totalPower"
+            placeholder="总电量"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.updateTime"
+            placeholder=""
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.validOrders"
+            placeholder="充电有效订单数"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+
+
+        <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+      </el-form>
+
+      <el-table
+          border
+          stripe="stripe"
+          :height="state.tableData.height"
+          highlight-current-row
+          current-row-key="id"
+          row-key="id"
+          :data="state.tableData.data"
+          v-loading="state.tableData.loading"
+          @selection-change="handleTableSelectionChange"
+          @sort-change="handleTableSortChange">
+        <template #empty>
+          <el-empty></el-empty>
+        </template>
+        <el-table-column type="selection" align="center" width="55" fixed="left"/>
+        <el-table-column
+            v-for="field in state.tableData.columns"
+            :key="field.prop"
+            :label="field.label"
+            :column-key="field.prop"
+            :width="field.width"
+            :min-width="field.minWidth"
+            :fixed="field.fixed"
+            :sortable="field.sortable"
+            :show-overflow-tooltip="!field.fixed&&field.width>150"
+        >
+          <template #default="{row}">
+            <template v-if="field.prop==='expand'">
+              <p style="padding-left: 2em;" v-html="row[field.prop]"></p>
+            </template>
+            <template v-else>
+              <div>{{ row[field.prop] }}</div>
+            </template>
+
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
+    </el-card>
+  </div>
+  <StationStatementsMonthDialog ref="stationStatementsMonthDialogRef" @refresh="loadData(true)"/>
+</template>
+
+<script setup lang="ts" name="StationStatementsMonthList">
+import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
+import {$body, $get} from "/@/utils/request";
+import {Msg} from "/@/utils/message";
+
+
+import ExtPage from '/@/components/form/ExtPage.vue'
+
+import mittBus from '/@/utils/mitt';
+
+const StationStatementsMonthDialog = defineAsyncComponent(() => import("/@/views/admin/station/statment/dialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const stationStatementsMonthDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array<any>,
+    loading: false,
+    columns: [
+      {type: 'selection', width: 60, align: 'center', fixed: 'left'},
+      {label: '订单平均充电费用', prop: 'avgOrderMoney', resizable: true, width: 130},
+      {label: '充电人数', prop: 'chargeUsers', resizable: true, width: 130},
+      {label: '', prop: 'createTime', sortable: 'custom', resizable: true, width: 150},
+      {label: '总优惠金额', prop: 'discountAmount', resizable: true, width: 130},
+      {label: '总电费', prop: 'elecMoney', resizable: true, width: 130},
+      {label: '总服务费', prop: 'serviceMoney', resizable: true, width: 130},
+      {label: '服务费优惠金额', prop: 'serviceMoneyDiscount', resizable: true, width: 130},
+      {label: '统计时间', prop: 'statMonth', resizable: true},
+      {label: '站点id', prop: 'stationId', resizable: true},
+      {label: '总充电费用', prop: 'totalMoney', resizable: true, width: 130},
+      {label: '', prop: 'updateTime', sortable: 'custom', resizable: true, width: 150},
+      {label: '充电有效订单数', prop: 'validOrders', resizable: true, width: 130},
+      {
+        label: '操作', prop: 'action', width: 180, align: 'center', fixed: 'right',
+      }
+    ],
+  },
+})
+
+
+// 监听双向绑定 modelValue 的变化
+// watch(
+//         () => state.pageIndex,
+//         () => {
+//
+//         }
+// );
+
+//生命周期钩子
+onBeforeMount(() => {
+})
+
+onMounted(() => {
+  loadData();
+
+  nextTick(() => {
+    let bodyHeight = document.body.clientHeight;
+    let queryHeight = queryRef.value.$el.clientHeight;
+    state.tableData.height = bodyHeight - queryHeight - 220
+  })
+
+  mittBus.on("stationStatementsMonth.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("stationStatementsMonth.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $body(`/stationStatementsMonth/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
+    let {list, total} = res;
+    state.tableData.data = list;
+    state.pageQuery.total = total;
+    state.tableData.loading = false;
+  }).catch(e => {
+    console.error(e)
+    state.tableData.loading = false;
+  })
+};
+
+// 打开修改站点经营数据对账表-月弹窗
+const onRowClick = (type: string, row: any) => {
+  stationStatementsMonthDialogRef.value.open(type, row);
+};
+
+// 删除站点经营数据对账表-月
+const onRowDel = (row: any) => {
+  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
+    $get(`/stationStatementsMonth/delete/${row.id}`).then(() => {
+      Msg.message("删除成功", 'success')
+    }).catch(() => {
+      Msg.message("删除失败", 'error')
+    })
+  });
+};
+
+const handleTableSelectionChange = (selection: any) => {
+  console.log("handleTableSelectionChange>>", selection)
+  // emit("on-check-change", selection)
+}
+
+const handleTableSortChange = (column, prop, order) => {
+  console.log("handleTableSortChange>>", column, prop, order)
+  // emit("on-sort-change", column)
+}
+
+
+//endregion
+
+
+// 暴露变量
+// defineExpose({
+//     loadData,
+// });
+</script>

+ 1 - 2
admin/src/main/java/com/kym/admin/controller/AdminUserController.java

@@ -86,8 +86,7 @@ public class AdminUserController extends IController {
     @SaCheckPermission("user.add")
     @PostMapping("add")
     R<?> createAdminUser(@RequestBody AdminUserVo adminUserVo) {
-        adminUserService.createAdminUser(adminUserVo);
-        return R.success();
+        return resp(()->adminUserService.createAdminUser(adminUserVo));
     }
 
 

+ 9 - 0
admin/src/main/java/com/kym/admin/controller/DataDictController.java

@@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 
 /**
  * <p>
@@ -59,4 +61,11 @@ public class DataDictController extends IController {
     }
 
 
+    @SysLog("查询字典保存更新接口")
+    @PostMapping("saveOrUpdate")
+    public R<?> saveOrUpdate(@RequestBody List<DataDict> dictList) {
+        return resp((t) -> dataDictService.saveOrUpdateBatch(dictList));
+    }
+
+
 }

+ 2 - 0
entity/src/main/java/com/kym/entity/miniapp/DataDict.java

@@ -27,6 +27,8 @@ public class DataDict extends BaseEntity implements Serializable {
      */
     private String code;
 
+    private Long weight;
+
     /**
      * 名称
      */

+ 11 - 0
miniapp/src/main/java/com/kym/miniapp/controller/DataDictController.java

@@ -1,5 +1,7 @@
 package com.kym.miniapp.controller;
 
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
 import com.kym.common.IQuery;
 import com.kym.common.R;
 import com.kym.common.controller.IController;
@@ -11,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 
 /**
  * <p>
@@ -56,4 +60,11 @@ public class DataDictController extends IController {
     }
 
 
+    @SaCheckPermission(value = {"dict.add", "dict.modify"}, mode = SaMode.OR)
+    @PostMapping("saveOrUpdate")
+    public R<?> saveOrUpdate(@RequestBody List<DataDict> dictList) {
+        return resp((t) -> dataDictService.saveOrUpdate(dictList));
+    }
+
+
 }

+ 4 - 0
service/src/main/java/com/kym/service/miniapp/DataDictService.java

@@ -4,6 +4,8 @@ import com.github.yulichang.base.MPJBaseService;
 import com.kym.common.IQuery;
 import com.kym.entity.miniapp.DataDict;
 
+import java.util.List;
+
 
 /**
  * <p>
@@ -18,4 +20,6 @@ public interface DataDictService extends MPJBaseService<DataDict> {
     Object list(IQuery<DataDict> query);
 
     Object listV2(IQuery<DataDict> query);
+
+    void saveOrUpdate(List<DataDict> dictList);
 }

+ 15 - 2
service/src/main/java/com/kym/service/miniapp/impl/DataDictServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.util.ClassUtil;
 import cn.hutool.core.util.ReflectUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
+import com.github.pagehelper.PageHelper;
 import com.github.yulichang.base.MPJBaseServiceImpl;
 import com.kym.common.IQuery;
 import com.kym.common.utils.CommUtil;
@@ -12,10 +13,12 @@ import com.kym.entity.miniapp.DataDict;
 import com.kym.mapper.miniapp.DataDictMapper;
 import com.kym.service.miniapp.DataDictService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.List;
+import java.util.stream.Collectors;
 
 
 /**
@@ -33,8 +36,9 @@ public class DataDictServiceImpl extends MPJBaseServiceImpl<DataDictMapper, Data
 
     @Override
     public Object list(IQuery<DataDict> query) {
+        QueryChainWrapper<DataDict> wrapper = getWrapper(query.query, DataDict.class);
 //        PageHelper.startPage(query.pageNum, query.pageSize);
-        List<DataDict> list = list();
+        List<DataDict> list = wrapper.list();
         return new PageBean<>(list);
     }
 
@@ -42,9 +46,18 @@ public class DataDictServiceImpl extends MPJBaseServiceImpl<DataDictMapper, Data
     @Override
     public Object listV2(IQuery<DataDict> query) {
         QueryChainWrapper<DataDict> wrapper = getWrapper(query.query, DataDict.class);
+        wrapper.select("distinct(code)");
         Long count = wrapper.count();
+        PageHelper.startPage(query.pageNum, query.pageSize);
         List<DataDict> list = wrapper.list();
-        return IQuery.newPageBean(count, list);
+        List<DataDict> list1 = lambdaQuery().in(DataDict::getCode, list.stream().map(DataDict::getCode).collect(Collectors.toList())).list();
+        return IQuery.newPageBean(count, list1.stream().filter(CommUtil.distinctByKey(DataDict::getCode)).toList());
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void saveOrUpdate(List<DataDict> dictList) {
+        saveOrUpdateBatch(dictList);
     }