Parcourir la source

1. 站点图片
2. 活动新增
3.banner 新增、修改
4.退款

zuy il y a 2 ans
Parent
commit
d67db7c7a5
27 fichiers modifiés avec 2154 ajouts et 54 suppressions
  1. 29 13
      admin-web/src/components/form/ExtDSelect.vue
  2. 7 3
      admin-web/src/components/form/ExtDatePicker.vue
  3. 105 0
      admin-web/src/components/form/ExtImage.vue
  4. 1 1
      admin-web/src/components/form/ExtUpload.vue
  5. 36 5
      admin-web/src/router/route.ts
  6. 13 0
      admin-web/src/theme/app.scss
  7. 1 0
      admin-web/src/utils/u.ts
  8. 255 0
      admin-web/src/views/admin/activity/dialog.vue
  9. 294 0
      admin-web/src/views/admin/activity/index.vue
  10. 213 0
      admin-web/src/views/admin/banner/dialog.vue
  11. 270 0
      admin-web/src/views/admin/banner/index.vue
  12. 149 0
      admin-web/src/views/admin/invoice/InvoiceDialog.vue
  13. 280 0
      admin-web/src/views/admin/invoice/index.vue
  14. 0 13
      admin-web/src/views/admin/marketing/index.vue
  15. 341 0
      admin-web/src/views/admin/station/list/dialog.vue
  16. 8 5
      admin-web/src/views/admin/station/list/index.vue
  17. 1 1
      admin/src/main/java/com/kym/admin/controller/ActivityController.java
  18. 5 5
      admin/src/main/java/com/kym/admin/controller/BannerController.java
  19. 47 0
      admin/src/main/java/com/kym/admin/controller/InvoiceController.java
  20. 1 1
      admin/src/main/java/com/kym/admin/controller/StationController.java
  21. 5 2
      entity/src/main/java/com/kym/entity/admin/Banner.java
  22. 50 0
      entity/src/main/java/com/kym/entity/admin/queryParams/InvoiceQueryParam.java
  23. 14 0
      miniapp/src/main/java/com/kym/miniapp/controller/InvoiceController.java
  24. 6 2
      service/src/main/java/com/kym/service/admin/impl/ActivityServiceImpl.java
  25. 1 1
      service/src/main/java/com/kym/service/admin/impl/BannerServiceImpl.java
  26. 3 1
      service/src/main/java/com/kym/service/miniapp/InvoiceService.java
  27. 19 1
      service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

+ 29 - 13
admin-web/src/components/form/ExtDSelect.vue

@@ -24,7 +24,7 @@
   </el-select>
 </template>
 <script setup lang="ts" name="ExtDSelect">
-import {onMounted, reactive, watch} from 'vue';
+import {onMounted, reactive, watch,nextTick} from 'vue';
 import {Session} from "/@/utils/storage";
 import u from "/@/utils/u";
 
@@ -78,10 +78,23 @@ const state = reactive({
 
 const emit = defineEmits(['update:modelValue', 'on-change']);
 
+const typeOf = (obj: any) => {
+  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
+}
+
+
 watch(() => props.modelValue, (val, oldVal) => {
-  console.log('ExtDSelect', val, oldVal)
-  state.dataVal = props.modelValue;
-})
+  console.log('ExtDSelect', val, oldVal,typeOf(val) )
+  nextTick(()=>{
+    if (typeOf(val) === "number") {
+      state.dataVal = val + "";
+    }else{
+      state.dataVal = val;
+    }
+  })
+
+},{immediate:true})
+
 
 const handleChange = (val: number | Array<number>) => {
   console.log("handleChange", val)
@@ -101,22 +114,25 @@ const setupColorStyle = (hex: string = "#000000") => {
   return v;
 }
 
-
-onMounted(() => {
-  state.dataVal = props.modelValue;
+const setupDicts = () => {
+  let dictList: Array<any> = [];
   if (!u.isEmptyOrNull(props.dataRange)) {
-    state.dicts = props.dataRange;
+    dictList = props.dataRange;
   } else {
-    const dicts = Session.get("dicts");
+    let dicts = Session.get("dicts");
     if (!u.isEmptyOrNull(dicts)) {
-      state.dicts = dicts[props.type];
+      dictList = dicts[props.type];
     } else {
-      state.dicts = state.dictList[props.type]
-      console.table(state.dicts)
+      dictList = state.dictList[props.type]
     }
-
   }
+  state.dicts = dictList;
+  console.table(dictList)
+}
+
 
+onMounted(() => {
+  setupDicts();
 });
 </script>
 

+ 7 - 3
admin-web/src/components/form/ExtDatePicker.vue

@@ -10,6 +10,7 @@
       clearable
       :placeholder="placeholder"
       :format="format"
+      :value-format="valueFormat"
       :readonly="readonly"
       v-model="state.dateValue"></el-date-picker>
 </template>
@@ -28,16 +29,18 @@ const props = defineProps({
    */
   type: {
     type: String,
+    default:'datetime'
   },
   /**
    * 时间显示的格式
    */
   format: {
     type: String,
-    default: 'YYYY-MM-DD'
+    default: 'YYYY-MM-DD HH:mm:ss'
   },
   valueFormat: {
-    type: String
+    type: String,
+    default:'YYYY-MM-DD HH:mm:ss'
   },
   readonly: {
     type: Boolean,
@@ -88,7 +91,8 @@ onMounted(()=>{
 
 watch(() => props.modelValue, (val:any, oldVal) => {
   console.log('props.modelValue', val, oldVal)
- setupDate(val);
+  state.dateValue = val;
+ // setupDate(val);
 /*
   if (!state.isRangeMode) {
     if (props.type === 'year') {

+ 105 - 0
admin-web/src/components/form/ExtImage.vue

@@ -0,0 +1,105 @@
+<template>
+  <div class="demo-image__preview">
+    <el-image
+        :z-index="99999"
+        style="width: 60px; height: 60px"
+        :src="state.url"
+        :zoom-rate="1.2"
+        :max-scale="7"
+        :min-scale="0.2"
+        :preview-src-list="state.previewList"
+        :initial-index="4"
+        fit="cover"
+        :preview-teleported="true"
+    />
+<!--    <el-dialog
+        v-model="state.visible"
+        fullscreen
+        draggable
+        destroy-on-close
+        :close-on-click-modal="false"
+        @close="state.visible=false">
+      <el-image-viewer
+          :url-list="state.previewList"
+          @close="close"
+      />
+    </el-dialog>-->
+
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onMounted, reactive, watch} from 'vue';
+import u from "/@/utils/u";
+
+const props = defineProps({
+  srcList: {
+    type: Array < String >,
+    default: () => {
+      return []
+    }
+  }
+})
+
+
+const open = () => {
+  state.visible = true;
+}
+
+
+const close = () => {
+  state.visible = false;
+}
+
+const state = reactive({
+  visible: false,
+  url: '',
+  previewList: []
+})
+
+watch(() => props.srcList, (nv, ov) => {
+  if (nv) {
+    state.previewList = [];
+    if (Array.isArray(nv)) {
+      nv.forEach((item: any) => {
+        state.previewList.push(u.fmt.fmtUrl(item));
+      })
+      state.url = state.previewList[0]
+    } else {
+      state.previewList = [u.fmt.fmtUrl(nv)];
+      state.url = state.previewList[0]
+    }
+    console.log(state.previewList)
+  }
+}, {immediate: true, deep: true})
+const url =
+    'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
+const srcList = [
+  'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
+  'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
+  'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
+  'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
+  'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
+  'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
+  'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',
+]
+
+defineExpose(
+    open
+)
+</script>
+
+<style scoped>
+.demo-image__error .image-slot {
+  font-size: 30px;
+}
+
+.demo-image__error .image-slot .el-icon {
+  font-size: 30px;
+}
+
+.demo-image__error .el-image {
+  width: 100%;
+  height: 100px;
+}
+</style>

+ 1 - 1
admin-web/src/components/form/ExtUpload.vue

@@ -72,7 +72,7 @@ const handleAvatarSuccess: UploadProps['onSuccess'] = (
 }
 
 const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
-  if (rawFile.type !== 'image/jpeg') {
+  if (rawFile.type !== 'image/jpeg'&&rawFile.type !== 'image/png') {
     ElMessage.error('Avatar picture must be JPG format!')
     return false
   } else if (rawFile.size / 1024 / 1024 > 2) {

+ 36 - 5
admin-web/src/router/route.ts

@@ -166,10 +166,25 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                            },*/
                 ]
             },
-         /*   {
-                path: '/marketing',
-                name: 'adminMarketing',
-                component: () => import('/@/views/admin/marketing/index.vue'),
+            {
+                path: '/banner',
+                name: 'adminBanner',
+                component: () => import('/@/views/admin/banner/index.vue'),
+                meta: {
+                    title: '横幅广告',
+                    isLink: '',
+                    isHide: false,
+                    isKeepAlive: true,
+                    isAffix: false,
+                    isIframe: false,
+                    icon: 'ele-PictureRounded',
+                    perm:"banner.list",
+                }
+            },
+            {
+                path: '/activity',
+                name: 'adminActivity',
+                component: () => import('/@/views/admin/activity/index.vue'),
                 meta: {
                     title: '营销活动',
                     isLink: '',
@@ -178,8 +193,9 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-PieChart',
+                    perm:"activity.list",
                 }
-            },*/
+            },
             {
                 path: '/ordering',
                 name: 'adminOrdering',
@@ -240,6 +256,21 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     perm:"refund.list",
                 },
             },
+            {
+                path: '/invoice',
+                name: 'adminInvoice',
+                component: () => import('/@/views/admin/invoice/index.vue'),
+                meta: {
+                    title: '发票管理',
+                    isLink: '',
+                    isHide: false,
+                    isKeepAlive: true,
+                    isAffix: false,
+                    isIframe: false,
+                    icon: 'ele-Tickets',
+                    perm:"invoice.list",
+                },
+            },
             {
                 path: '/org',
                 name: 'adminOrg',

+ 13 - 0
admin-web/src/theme/app.scss

@@ -285,6 +285,19 @@ body,
 	min-height: 100vh !important;
 }
 
+.wd100 {
+	width: 100px !important;
+}
+
+.wd150 {
+	width: 150px !important;
+}
+
+.wd200 {
+	width: 200px !important;
+}
+
+
 /* 颜色值
 ------------------------------- */
 .color-primary {

+ 1 - 0
admin-web/src/utils/u.ts

@@ -498,6 +498,7 @@ const u = {
         num: {type: 'string', pattern: /^\d*$/, message: "只能输入数字"},
         letter: {type: 'string', pattern: /^[A-Za-z0-9\-]$/, message: "只能输入字母和数字"},
         tel: {type: 'string', pattern: /^[-\d]*$/, message: "电话只能包含数字和-"},
+        url: {type: 'string', pattern: /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/, message: "链接地址错误"},
     },
     fmt: {
         fmtMoney:function (money:number) {

+ 255 - 0
admin-web/src/views/admin/activity/dialog.vue

@@ -0,0 +1,255 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+  <div class="system-dialog-container">
+    <el-dialog
+        :title="state.dialog.title"
+        v-model="state.dialog.isShowDialog"
+        width="820px"
+        draggable
+        destroy-on-close
+        :close-on-click-modal="false"
+        @close="onClose"
+        align-center>
+      <el-form
+          inline
+          :model="state.ruleForm"
+          :rules="state.rules"
+          ref="formRef"
+          size="default"
+          label-width="150px"
+          class="mt5">
+        <el-form-item label="活动名称" prop="name" class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.name"
+              placeholder="活动名称"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <ext-date-picker
+              v-model.trim="state.ruleForm.startTime"
+              placeholder="开始时间"
+              type="datetime"
+              clearable
+              class="wd200">
+          </ext-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <ext-date-picker
+              v-model.trim="state.ruleForm.endTime"
+              placeholder="结束时间"
+              type="datetime"
+              clearable
+              class="wd200">
+          </ext-date-picker>
+        </el-form-item>
+        <el-form-item label="优惠方式"  prop="discountType">
+          <ext-d-select
+              v-model="state.ruleForm.discountType"
+              placeholder="优惠方式"
+              type="Activity.discountType"
+              clearable
+              class="wd200 ">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="目标用户" prop="targetUsers">
+          <ext-d-select
+              v-model="state.ruleForm.targetUsers"
+              placeholder="目标用户"
+              type="Activity.targetUsers"
+              clearable
+              class="wd200 ">
+          </ext-d-select>
+        </el-form-item>
+
+        <el-form-item label="优惠允许叠加" prop="allowStacke">
+          <ext-d-select
+              v-model="state.ruleForm.allowStacke"
+              placeholder="优惠允许叠加"
+              type="Activity.allowStacke"
+              clearable
+              class="wd200 ">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="数量限制" prop="quantity">
+          <el-input
+              type="number"
+              v-model.trim="state.ruleForm.quantity"
+              placeholder="数量限制"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="活动状态" prop="status">
+          <ext-d-select
+              v-model="state.ruleForm.status"
+              placeholder="活动状态"
+              type="Activity.status"
+              clearable
+              class="wd200 ">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark" class="w100">
+          <el-input
+              maxlength="500"
+              show-word-limit
+              type="textarea"
+              :rows="3"
+              v-model.trim="state.ruleForm.remark"
+              placeholder="备注信息"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+
+        <el-divider content-position="left">关联站点</el-divider>
+        <el-form-item label="适用站点" prop="applyStation">
+          <ext-d-select
+              @on-change="handleStationChange"
+              v-model="state.ruleForm.applyStation"
+              placeholder="适用站点"
+              type="Activity.applyStation"
+              clearable
+              class="wd200 ">
+          </ext-d-select>
+
+          <ext-select
+              v-model="state.ruleForm.stationIds"
+              multiple
+              placeholder="关联站点"
+              url="station/listStation"
+              url-method="get"
+              label-key="stationName"
+              value-key="stationId"
+              clearable
+              class="w100 mt5">
+          </ext-select>
+        </el-form-item>
+
+
+        <el-divider content-position="left">关联权益</el-divider>
+
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="onCancel" size="default">取 消</el-button>
+          <el-button v-if="state.action==='add'||state.action==='edit'"  :loading="state.btnLoading" type="primary" @click="onSubmit" size="default">{{ state.dialog.submitTxt }}</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="ActivityDialog">
+import {reactive, ref} from 'vue';
+import {Msg} from "/@/utils/message";
+import {$body, $get} from "/@/utils/request";
+import u from '/@/utils/u'
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtSelect from "/@/components/form/ExtSelect.vue";
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = () => ({
+  action:'',
+  ruleForm: {
+    id: 0
+  },
+  btnLoading: false,
+  dialog: {
+    isShowDialog: false,
+    type: '',
+    title: '',
+    submitTxt: '',
+  },
+  rules: {
+    name:[u.validator.required],
+    startTime:[u.validator.required],
+    endTime:[u.validator.required],
+    discountType:[u.validator.required],
+    targetUsers:[u.validator.required],
+    applyStation:[u.validator.required],
+    allowStacke:[u.validator.required],
+    quantity:[u.validator.required],
+    status:[u.validator.required],
+  },
+})
+
+// 定义变量内容
+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;
+  state.action = action;
+  if (action !== 'add') {
+    loadData(row.id);
+  } else {
+    state.ruleForm = Object.assign(state.ruleForm, row);
+  }
+};
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.isShowDialog = false;
+  Object.assign(state, initState())
+};
+// 取消
+const onCancel = () => {
+  onClose();
+};
+// 提交
+const onSubmit = () => {
+  formRef.value.validate((valid:boolean) => {
+    if(valid){
+      state.btnLoading = true;
+      const url = state.ruleForm.id > 0 ? "activity/modify" : "activity/add"
+      $body(url, state.ruleForm).then(() => {
+        state.btnLoading = false;
+        Msg.message('操作成功');
+        console.log('submit!')
+        onClose();
+        emit('refresh');
+      })
+    }
+  }).catch(() => {
+    state.btnLoading = false;
+    Msg.message('请先完整填写表单', 'error');
+  })
+};
+
+const handleFormChange = (formData: any) => {
+  console.log(formData)
+}
+
+// 初始化表格数据
+const loadData = (id: number) => {
+  $get(`activity/${id}`).then((res: any) => {
+    state.ruleForm = res;
+  })
+}
+
+const handleStationChange = (applyStation:number)=>{
+  console.log(applyStation)
+  if(applyStation==0){
+    // state.ruleForm.stationIds = [];
+  }
+
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

+ 294 - 0
admin-web/src/views/admin/activity/index.vue

@@ -0,0 +1,294 @@
+<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.name"
+            placeholder="活动名称"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <ext-date-picker
+            v-model="state.formQuery.startTime"
+            placeholder="开始时间"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-date-picker>
+        <ext-date-picker
+            v-model="state.formQuery.endTime"
+            placeholder="结束时间"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-date-picker>
+        <ext-d-select
+            v-model="state.formQuery.discountType"
+            placeholder="优惠方式"
+            type="Activity.discountType"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+
+        <ext-d-select
+            v-model="state.formQuery.status"
+            placeholder="活动状态"
+            type="Activity.status"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+
+        <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
+          <SvgIcon name="ele-Search"/>
+          查询
+        </el-button>
+        <el-button v-auth="'activity.add'" size="default" plain type="success" class="ml10" @click="onRowClick('add',null)">
+          <SvgIcon name="ele-FolderAdd"/>
+          新增
+        </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"
+          @row-dblclick="onRowClick('view',$event)"
+          @selection-change="handleTableSelectionChange"
+          @sort-change="handleTableSortChange">
+        <template #empty>
+          <el-empty></el-empty>
+        </template>
+        <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-if="field.prop==='period'">
+              {{ row.startTime }} ~ {{ row.endTime }}
+            </template>
+            <template v-else-if="field.prop==='discountType'">
+              <ext-d-label type="Activity.discountType" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='applyStation'">
+              <ext-d-label type="Activity.applyStation" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='status'">
+              <ext-d-label type="Activity.status" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='targetUsers'">
+              <ext-d-label type="Activity.targetUsers" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='allowStacke'">
+              <ext-d-label type="Activity.allowStacke" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='action'">
+              <el-button v-auth="'banner.modify'" size="small" plain type="warning" @click="onRowClick('edit',row)">编辑</el-button>
+              <el-button v-auth="'banner.modify'" size="small" plain type="danger" @click="onRowTerminal(row)">终止</el-button>
+            </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>
+  <ActivityDialog ref="activityDialogRef" @refresh="loadData(true)"/>
+</template>
+
+<script setup lang="ts" name="ActivityList">
+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';
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+import ExtDLabel from "/@/components/form/ExtDLabel.vue";
+
+const ActivityDialog = defineAsyncComponent(() => import("/@/views/admin/activity/dialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const activityDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array<any>,
+    loading: false,
+    columns: [
+      {
+        label: '活动名称', prop: 'name', resizable: true, width: 170, fixed: 'left'
+      },
+      {label: '活动时间', prop: 'period', resizable: true, width: 350},
+      // {label: '结束时间', prop: 'endTime',  resizable: true, width: 150},
+      {label: '活动状态', prop: 'status', align: 'center', width: 130},
+      {label: '优惠方式', prop: 'discountType', resizable: true, width: 130},
+      {label: '目标用户', prop: 'targetUsers', resizable: true, width: 130},
+      {label: '适用站点', prop: 'applyStation', resizable: true, width: 130},
+      {label: '优惠允许叠加', prop: 'allowStacke', resizable: true, width: 130},
+      {label: '数量限制', prop: 'quantity', resizable: true, width: 130},
+      {label: '创建时间', prop: 'createTime', resizable: true, width: 180},
+      {label: '更新时间', prop: 'updateTime', resizable: true, width: 180},
+      {
+        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 - 320
+  })
+
+  mittBus.on("activity.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("activity.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $get(`/activity`, {...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 handleRowClick = (event) => {
+  console.log(event)
+  activityDialogRef.value.open("view", event);
+};
+
+// 打开修改用户弹窗
+const onRowClick = (type: string, row: any) => {
+  activityDialogRef.value.open(type, row);
+};
+
+// 删除用户
+const onRowTerminal = (row: any) => {
+  Msg.confirm(`此操作将终止:『${row.name}』,是否继续?`).then(() => {
+    $get(`/activity/terminateActivity/${row.id}`).then(() => {
+      Msg.message("终止成功", 'success')
+      loadData(true)
+    }).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>

+ 213 - 0
admin-web/src/views/admin/banner/dialog.vue

@@ -0,0 +1,213 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+  <div class="system-dialog-container">
+    <el-dialog
+        :title="state.dialog.title"
+        v-model="state.dialog.isShowDialog"
+        width="820px"
+        draggable
+        destroy-on-close
+        :close-on-click-modal="false"
+        @close="onClose"
+        align-center>
+      <el-form
+          inline
+          :model="state.ruleForm"
+          :rules="state.rules"
+          ref="formRef"
+          size="default"
+          label-width="125px"
+          class="mt5">
+        <el-form-item label="主活动" prop="activityId">
+          <ext-select
+              v-model="state.ruleForm.activityId"
+              placeholder="关联活动"
+              url="activity/list"
+              clearable
+              class="wd200">
+          </ext-select>
+        </el-form-item>
+        <el-form-item label="banner图片" prop="bannerUrl">
+          <ext-upload v-model="state.ruleForm.bannerUrl"></ext-upload>
+<!--          <el-input-->
+<!--              v-model.trim="state.ruleForm.bannerUrl"-->
+<!--              placeholder="banner图片地址"-->
+<!--              clearable-->
+<!--              class="wd200">-->
+<!--          </el-input>-->
+        </el-form-item>
+        <el-form-item label="描述" prop="bannerDesc"   class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.bannerDesc"
+              placeholder="描述"
+              maxlength="200"
+              show-word-limit
+              type="textarea"
+              :rows="2"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+
+        <el-form-item label="关联跳转地址" prop="linkUrl"  class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.linkUrl"
+              placeholder="关联跳转地址"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <ext-date-picker
+              v-model.trim="state.ruleForm.startTime"
+              placeholder="开始时间"
+              type="datetime"
+              clearable
+              class="wd200">
+          </ext-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTime">
+          <ext-date-picker
+              v-model.trim="state.ruleForm.endTime"
+              placeholder="结束时间"
+              type="datetime"
+              clearable
+              class="wd200">
+          </ext-date-picker>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <ext-d-select
+              v-model.trim="state.ruleForm.status"
+              type="Banner.status"
+              placeholder="状态"
+              clearable
+              class="wd200">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark" class="w100">
+          <el-input
+              maxlength="500"
+              show-word-limit
+              type="textarea"
+              :rows="3"
+              v-model.trim="state.ruleForm.remark"
+              placeholder="备注信息"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+      </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-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="BannerDialog">
+import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
+import {Msg} from "/@/utils/message";
+import {$body, $get} from "/@/utils/request";
+import u from '/@/utils/u'
+import ExtSelect from "/@/components/form/ExtSelect.vue";
+import ExtUpload from "/@/components/form/ExtUpload.vue";
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = () => ({
+  ruleForm: {
+    id: 0,
+    status:1
+  },
+  btnLoading: false,
+  dialog: {
+    isShowDialog: false,
+    type: '',
+    title: '',
+    submitTxt: '',
+  },
+  rules: {
+    startTime:[u.validator.required],
+    endTime:[u.validator.required],
+    status:[u.validator.required],
+    linkUrl:[u.validator.url],
+  },
+})
+
+// 定义变量内容
+const state = reactive(initState());
+
+
+// 打开弹窗
+const open = (action: string = 'add', row: any) => {
+  state.dialog.title = u.dialog.actions[action].title + "『banner配置』"
+  state.dialog.submitTxt = u.dialog.actions[action].btn + "『banner配置』"
+  state.dialog.isShowDialog = true;
+  if (action !== 'add') {
+    loadData(row.id);
+  } else {
+    state.ruleForm = Object.assign(state.ruleForm, row);
+  }
+};
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.isShowDialog = false;
+  Object.assign(state, initState())
+};
+// 取消
+const onCancel = () => {
+  onClose();
+};
+// 提交
+const onSubmit = () => {
+  formRef.value.validate((valid:boolean) => {
+    if(valid){
+      if(new Date(state.ruleForm.startTime).getTime()>new Date(state.ruleForm.endTime).getTime()){
+        Msg.message("开始时间不能晚于结束时间","error")
+        return false;
+      }
+      state.btnLoading = true;
+      const url = state.ruleForm.id > 0 ? "banner/modify" : "banner/add"
+      $body(url, state.ruleForm).then(() => {
+        state.btnLoading = false;
+        Msg.message('操作成功');
+        console.log('submit!')
+        onClose();
+        emit('refresh');
+      })
+    }
+  }).catch(() => {
+    state.btnLoading = false;
+    Msg.message('请先完整填写表单', 'error');
+  })
+};
+
+const handleFormChange = (formData: any) => {
+  console.log(formData)
+}
+
+// 初始化表格数据
+const loadData = (id: number) => {
+  $get(`banner/${id}`).then((res: any) => {
+    state.ruleForm = res;
+  })
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

+ 270 - 0
admin-web/src/views/admin/banner/index.vue

@@ -0,0 +1,270 @@
+<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">
+
+        <ext-date-picker
+            v-model="state.formQuery.startTime"
+            placeholder="开始时间"
+            type="datetime"
+            clearable
+            @blur="loadData(true)"
+            class="wd200 mr10">
+        </ext-date-picker>
+        <ext-date-picker
+            v-model="state.formQuery.endTime"
+            placeholder="结束时间"
+            type="datetime"
+            clearable
+            @blur="loadData(true)"
+            class="wd200 mr10">
+        </ext-date-picker>
+        <ext-d-select
+            v-model="state.formQuery.status"
+            placeholder="状态"
+            type="Banner.status"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+
+        <el-input
+            v-model="state.formQuery.bannerDesc"
+            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  v-auth="'banner.add'"   size="default" plain  type="success" class="ml10" @click="onRowClick('add',null)">
+          <SvgIcon name="ele-FolderAdd"/>
+          新增
+        </el-button>
+      </el-form>
+
+      <el-table
+          border
+          :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-if="field.prop==='bannerUrl'">
+              <ext-image :src-list="row[field.prop]" ></ext-image>
+<!--             <ext-upload v-model="row[field.prop]"></ext-upload>-->
+            </template>
+            <template v-else-if="field.prop==='period'">
+              {{u.fmt.fmtDateTime(row.startTime)}}~{{u.fmt.fmtDateTime(row.endTime)}}
+            </template>
+            <template v-else-if="field.prop==='status'">
+              <ext-d-label type="Banner.status" v-model="row[field.prop]"/>
+            </template>
+            <template v-else-if="field.prop==='action'">
+              <el-button  v-auth="'banner.modify'"  size="small" plain  type="warning" @click="onRowClick('edit',row)">编辑</el-button>
+<!--              <el-button v-auth="'banner.delete'"   size="small" plain  type="danger" @click="onRowDel(row)">删除</el-button>-->
+            </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>
+  <BannerDialog ref="bannerDialogRef" @refresh="loadData(true)"/>
+</template>
+
+<script setup lang="ts" name="BannerList">
+import {defineAsyncComponent, reactive, onMounted, onBeforeMount, ref, getCurrentInstance, nextTick, onBeforeUnmount} from 'vue';
+import {$body, $get} from "/@/utils/request";
+import {Msg} from "/@/utils/message";
+import u from "/@/utils/u";
+
+
+import ExtPage from '/@/components/form/ExtPage.vue'
+
+import mittBus from '/@/utils/mitt';
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtDLabel from "/@/components/form/ExtDLabel.vue";
+import ExtUpload from "/@/components/form/ExtUpload.vue";
+import ExtImage from "/@/components/form/ExtImage.vue";
+
+const BannerDialog = defineAsyncComponent(() => import("/@/views/admin/banner/dialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const bannerDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array<any>,
+    loading: false,
+    columns: [
+      {label: 'banner', prop: 'bannerUrl', resizable: true,width: 100},
+      {label: '活动时间', prop: 'period',  resizable: true, width: 350},
+      {label: '状态', prop: 'status', sortable: 'custom', align: 'center', width: 180},
+      {label: '关联跳转地址', prop: 'linkUrl', resizable: true, width: 220},
+      {label: '描述', prop: 'bannerDesc', resizable: true, width: 200},
+      {label: '创建时间', prop: 'createTime', sortable: 'custom', resizable: true, width: 180},
+      {label: '更新时间', prop: 'updateTime', sortable: 'custom', resizable: true, width: 180},
+      {
+        label: '操作', prop: 'action', width: 100, 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 - 320
+  })
+
+  mittBus.on("banner.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("banner.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $body(`/banner/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) => {
+  bannerDialogRef.value.open(type, row);
+};
+
+// 删除用户
+const onRowDel = (row: any) => {
+  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
+    $get(`/banner/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>

+ 149 - 0
admin-web/src/views/admin/invoice/InvoiceDialog.vue

@@ -0,0 +1,149 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+    <div class="system-dialog-container">
+        <el-dialog
+                :title="state.dialog.title"
+                v-model="state.dialog.isShowDialog"
+                width="820px"
+                draggable
+                destroy-on-close
+                :close-on-click-modal="false"
+                align-center>
+            <ext-detail-form
+                    ref="formRef"
+                    v-model="state.ruleForm"
+                    :columns="state.columns"
+                    :rules="state.rules"
+                    @on-change="handleFormChange"
+            ></ext-detail-form>
+
+            <template #footer>
+				<span 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>
+				</span>
+            </template>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup lang="ts" name="InvoiceDialog">
+    import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
+    import {Msg} from "/@/utils/message";
+    import {$body, $get} from "/@/utils/request";
+    import u from '/@/utils/u'
+
+    // 引入组件
+    const ExtDetailForm = defineAsyncComponent(() => import('/@/components/form/ExtDetailForm.vue'));
+
+    // 定义子组件向父组件传值/事件
+    const emit = defineEmits(['refresh']);
+    const formRef = ref();
+    //定义初始变量,重置使用
+    const initState = ()=>({
+        ruleForm: {
+            id:0
+        },
+        btnLoading: false,
+        dialog: {
+            isShowDialog: false,
+            type: '',
+            title: '',
+            submitTxt: '',
+        },
+        columns: [
+                                                                                                                                                                                                        {label: '微信发票申请id', prop: 'applyId', type: 'text',},
+                                                                                                                                                                                                                                                            {label: '发票抬头填写人的openid', prop: 'openid', type: 'text',},
+                                                                                                                    {label: '发票关联订单详情', prop: 'orderDetails', type: 'user'},
+                                                                                                                                                                                                                        {label: '累积总金额(元)', prop: 'totalMoney', type: 'user',},
+                                                                                                                                                                                {label: '累积电费(元)', prop: 'elecMoney', type: 'user',},
+                                                                                                                                                                                {label: '累积服务费(元)', prop: 'serviceMoney', type: 'user',},
+                                                                                                                                                                                                                {label: '接收发票邮箱', prop: 'email', type: 'text',},
+                                                                                                                                                                                {label: '电话', prop: 'phone', type: 'text',},
+                                                                                                                                                                                {label: '发票类型:INDIVIDUAL-个人 ORGANIZATION-企业', prop: 'invoiceType', type: 'text',},
+                                                                                                                                                                                {label: '发票抬头名称', prop: 'invoiceTitle', type: 'text',},
+                                                                                                                                                                                {label: '公司税号', prop: 'taxId', type: 'text',},
+                                                                                                                                                                                {label: '公司地址', prop: 'address', type: 'text',},
+                                                                                                                                                                                {label: '开户银行', prop: 'bankName', type: 'text',},
+                                                                                                                                                                                {label: '银行账户', prop: 'bankAccount', type: 'text',},
+                                                                                                                                                {label: '发票金额(单位:分)', prop: 'invoiceAmount', type: 'user',},
+                                                                                                                                                    {label: '税额详情信息', prop: 'taxInfo', type: 'user'},
+                                                                                                                                                                            {label: '开票人', prop: 'biller', type: 'text',},
+                                                                                                                            {
+                            label: '发票状态:0-待开票 1-已开票 2-已作废',
+                            prop: 'status',
+                            sortable: 'custom',
+                            align: 'center',
+                            type: 'dict',
+                            conf: {dict: 'Invoice.status'}
+                        },
+                                                                {label: '发票状态:0-待开票 1-已开票 2-已作废', prop: 'status', type: 'user',},
+                                                                                                                                                                                                                {label: '备注', prop: 'remark', type: 'text',},
+                                                                                                                                                    {                        label: '', prop: 'createTime', type: 'datetime',},
+                                                                                                                {                        label: '', prop: 'updateTime', type: 'datetime',},
+                                    ],
+        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);
+        }else{
+            state.ruleForm = Object.assign(state.ruleForm,row);
+        }
+    };
+    // 关闭弹窗
+    const onClose = () => {
+        state.dialog.isShowDialog = false;
+        Object.assign(state,initState())
+    };
+    // 取消
+    const onCancel = () => {
+        onClose();
+    };
+    // 提交
+    const onSubmit = () => {
+        formRef.value.checkForm().then(() => {
+            state.btnLoading = true;
+            const url = state.ruleForm.id > 0 ? "invoice/modify" : "invoice/add"
+                $body(url, state.ruleForm).then(() => {
+                state.btnLoading = false;
+                Msg.message('操作成功');
+                console.log('submit!')
+                onClose();
+                emit('refresh');
+            })
+        }).catch(() => {
+            state.btnLoading = false;
+            Msg.message('请先完整填写表单', 'error');
+        })
+    };
+
+    const handleFormChange = (formData: any) => {
+        console.log(formData)
+    }
+
+    // 初始化表格数据
+    const loadData = (id: number) => {
+            $get(`invoice/detail/${id}`).then((res: any) => {
+            state.ruleForm = res;
+        })
+    }
+
+    // 暴露变量
+    defineExpose({
+        open
+    });
+
+
+</script>

+ 280 - 0
admin-web/src/views/admin/invoice/index.vue

@@ -0,0 +1,280 @@
+<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">
+        <ext-d-select
+            v-model="state.formQuery.status"
+            placeholder="发票状态"
+            type="Invoice.status"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+        <ext-d-select
+            v-model="state.formQuery.invoiceType"
+            placeholder="发票类型"
+            type="Invoice.type"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+        <el-input
+            v-model="state.formQuery.email"
+            placeholder="接收发票邮箱"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.phone"
+            placeholder="电话"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+
+
+        <el-input
+            v-model="state.formQuery.invoiceTitle"
+            placeholder="发票抬头名称"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.taxId"
+            placeholder="公司税号"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.biller"
+            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
+            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>
+  <InvoiceDialog ref="invoiceDialogRef" @refresh="loadData(true)"/>
+</template>
+
+<script setup lang="ts" name="InvoiceList">
+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';
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+
+const InvoiceDialog = defineAsyncComponent(() => import("/@/views/page/InvoiceDialog.vue"));
+
+//定义引用
+const queryRef = ref();
+const invoiceDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {},
+  pageQuery: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0
+  },
+  tableData: {
+    height: 500,
+    data: [] as Array<any>,
+    loading: false,
+    columns: [
+      {label: '累积总金额(元)', prop: 'totalMoney', resizable: true, width: 130},
+      {label: '累积电费(元)', prop: 'elecMoney', resizable: true, width: 130},
+      {label: '累积服务费(元)', prop: 'serviceMoney', resizable: true, width: 130},
+      {label: '接收发票邮箱', prop: 'email', resizable: true},
+      {label: '电话', prop: 'phone', resizable: true},
+      {label: '发票类型', prop: 'invoiceType', resizable: true},
+      {label: '发票抬头名称', prop: 'invoiceTitle', resizable: true},
+      {label: '公司税号', prop: 'taxId', resizable: true},
+      {label: '公司地址', prop: 'address', resizable: true},
+      {label: '开户银行', prop: 'bankName', resizable: true},
+      {label: '银行账户', prop: 'bankAccount', resizable: true},
+      {label: '发票金额(单位:分)', prop: 'invoiceAmount', resizable: true, width: 130},
+      {label: '税额详情信息', prop: 'taxInfo', resizable: true, width: 150},
+      {label: '开票人', prop: 'biller', resizable: true},
+      {label: '发票状态', prop: 'status', sortable: 'custom', align: 'center'},
+      {label: '备注', prop: 'remark', resizable: true},
+      {label: '创建时间', prop: 'createTime', sortable: 'custom', resizable: true, width: 150},
+      {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("invoice.refresh", () => {
+    loadData();
+  })
+});
+
+onBeforeUnmount(() => {
+  mittBus.off("invoice.refresh")
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $body(`/invoice/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) => {
+  invoiceDialogRef.value.open(type, row);
+};
+
+// 删除用户
+const onRowDel = (row: any) => {
+  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
+    $get(`/invoice/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 - 13
admin-web/src/views/admin/marketing/index.vue

@@ -1,13 +0,0 @@
-<template>
-
-</template>
-
-<script>
-export default {
-  name: "index"
-}
-</script>
-
-<style scoped>
-
-</style>

+ 341 - 0
admin-web/src/views/admin/station/list/dialog.vue

@@ -0,0 +1,341 @@
+<style scoped lang="scss">
+
+</style>
+<template>
+  <div class="system-dialog-container">
+    <el-dialog
+        :title="state.dialog.title"
+        v-model="state.dialog.isShowDialog"
+        width="820px"
+        draggable
+        destroy-on-close
+        :close-on-click-modal="false"
+        @close="onClose"
+        align-center>
+      <el-form
+          inline
+          :model="state.ruleForm"
+          :rules="state.rules"
+          ref="formRef"
+          size="default"
+          label-width="125px"
+          class="mt5">
+        <el-form-item label="站点名称" prop="stationName">
+
+          <el-input
+              v-model.trim="state.ruleForm.stationName"
+              placeholder="站点名称"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="站点照片" prop="pictures">
+          <ext-upload v-model="state.ruleForm.pictures"></ext-upload>
+<!--          <el-input
+              v-model.trim="state.ruleForm.pictures"
+              placeholder="站点照片"
+              clearable
+              class="wd200">
+          </el-input>-->
+        </el-form-item>
+
+        <el-form-item label="en+充电站id" prop="stationId">
+          <el-input
+              v-model.trim="state.ruleForm.stationId"
+              placeholder="en+充电站id"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="en+运营商id" prop="operatorId">
+          <el-input
+              v-model.trim="state.ruleForm.operatorId"
+              placeholder="en+运营商id"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="设备机构代码" prop="equipmentOwnerId">
+          <el-input
+              v-model.trim="state.ruleForm.equipmentOwnerId"
+              placeholder="设备所属运营平台组织机构代码"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+
+        <el-form-item label="充电中国家代码" prop="countryCode">
+          <el-input
+              v-model.trim="state.ruleForm.countryCode"
+              placeholder="充电中国家代码:CN"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="省市辖区编码" prop="areaCode">
+          <el-input
+              v-model.trim="state.ruleForm.areaCode"
+              placeholder="充电站省市辖区编码"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="地址" prop="address" class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.address"
+              placeholder="地址"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="站点电话" prop="stationTel">
+          <el-input
+              v-model.trim="state.ruleForm.stationTel"
+              placeholder="站点电话"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="服务电话" prop="serviceTel">
+          <el-input
+              v-model.trim="state.ruleForm.serviceTel"
+              placeholder="服务电话"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="站点类型" prop="stationType">
+          <ext-d-select
+              type="Station.type"
+              v-model.trim="state.ruleForm.stationType"
+              placeholder="站点类型"
+              clearable
+              class="wd200">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="站点状态" prop="stationStatus">
+          <ext-d-select
+              v-model.trim="state.ruleForm.stationStatus"
+              placeholder="站点状态"
+              type="Station.status"
+              clearable
+              class="wd200">
+          </ext-d-select>
+        </el-form-item>
+        <el-form-item label="充电车位数量" prop="parkingNum">
+          <el-input
+              v-model.trim="state.ruleForm.parkingNum"
+              placeholder="充电车位数量"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="充电桩位置坐标" prop="location">
+          <el-input
+              v-model.trim="state.ruleForm.location"
+              placeholder="充电桩位置坐标"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+
+        <el-form-item label="建设场所" prop="construction">
+          <ext-d-select
+              type="Station.construction"
+              v-model.trim="state.ruleForm.construction"
+              placeholder="建设场所:1:居民区 2:公共机构 3:企事业单位 4:写字楼 5:工业园区 6:交通枢纽 7:大型文体设施 8:城市绿地 9:大型建筑配建停车场 10:路边停车位 11:城际高速服务区 255:其他"
+              clearable
+              class="wd200">
+          </ext-d-select>
+        </el-form-item>
+
+        <el-form-item label="充电费描述" prop="electricityFee">
+          <el-input
+              v-model.trim="state.ruleForm.electricityFee"
+              placeholder="充电费描述"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="服务费率描述" prop="serviceFee">
+          <el-input
+              v-model.trim="state.ruleForm.serviceFee"
+              placeholder="服务费率描述"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+
+
+        <el-form-item label="停车费" prop="parkFee">
+          <el-input
+              v-model.trim="state.ruleForm.parkFee"
+              placeholder="停车费"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="支付方式" prop="payment">
+          <el-input
+              v-model.trim="state.ruleForm.payment"
+              placeholder="支付方式:刷卡、线上、现金(电子钱包类卡为刷卡、身份鉴权卡、微信/支付宝、APP为线上)"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="是否支持预约" prop="supportOrder">
+          <el-input
+              v-model.trim="state.ruleForm.supportOrder"
+              placeholder="是否支持预约:0:不支持 1:支持"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input
+              v-model.trim="state.ruleForm.remark"
+              placeholder="备注"
+              clearable
+              class="wd200">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="站点引导" prop="siteGuide" class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.siteGuide"
+              placeholder="站点引导"
+              clearable
+              type="textarea"
+              class="w100">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="营业时间描述" prop="businessHours"  class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.businessHours"
+              placeholder="营业时间描述"
+              type="textarea"
+              clearable
+              class="w100">
+          </el-input>
+        </el-form-item>
+
+        <el-form-item label="使用车型描述" prop="matchCars"  class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.matchCars"
+              placeholder="使用车型描述"
+              clearable
+              type="textarea"
+              class="w100">
+          </el-input>
+        </el-form-item>
+        <el-form-item label="楼层及数量描述" prop="parkInfo"  class="w100">
+          <el-input
+              v-model.trim="state.ruleForm.parkInfo"
+              placeholder="车位楼层及数量描述"
+              clearable
+              type="textarea"
+              class="w100">
+          </el-input>
+        </el-form-item>
+      </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-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="StationDialog">
+import {defineAsyncComponent, reactive, onMounted, ref} from 'vue';
+import {Msg} from "/@/utils/message";
+import {$body, $get} from "/@/utils/request";
+import u from '/@/utils/u'
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtUpload from "/@/components/form/ExtUpload.vue";
+
+// 引入组件
+const ExtDetailForm = defineAsyncComponent(() => import('/@/components/form/ExtDetailForm.vue'));
+
+// 定义子组件向父组件传值/事件
+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;
+  state.ruleForm = Object.assign(state.ruleForm,row);
+};
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.isShowDialog = false;
+  Object.assign(state,initState())
+};
+// 取消
+const onCancel = () => {
+  onClose();
+};
+// 提交
+const onSubmit = () => {
+  formRef.value.validate((valid:boolean) => {
+    if(valid){
+      state.btnLoading = true;
+      const url = state.ruleForm.id > 0 ? "station/modify" : "station/add"
+      let params = {
+        stationId:state.ruleForm.stationId,
+        pictures:state.ruleForm.pictures
+      }
+      $body(url, params).then(() => {
+        state.btnLoading = false;
+        Msg.message('操作成功');
+        console.log('submit!')
+        onClose();
+        emit('refresh');
+      })
+    }
+  }).catch(() => {
+    state.btnLoading = false;
+    Msg.message('请先完整填写表单', 'error');
+  })
+};
+
+const handleFormChange = (formData: any) => {
+  console.log(formData)
+}
+
+// 初始化表格数据
+/*const loadData = (id: number) => {
+  $get(`station/detail/${id}`).then((res: any) => {
+    state.ruleForm = res;
+  })
+}*/
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

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

@@ -84,8 +84,8 @@
             :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 v-if="field.prop==='pictures'">
+              <ext-image :src-list="row[field.prop]" ></ext-image>
             </template>
             <template v-else-if="'stationType'===field.prop">
               <ext-d-label type="Station.type" v-model="row[field.prop]"/>
@@ -98,6 +98,7 @@
             </template>
             <template v-else-if="'action'===field.prop">
              <el-button link type="primary" @click="handleGotoEndpoint(row)">查看电桩</el-button>
+              <el-button  size="small" plain type="warning" @click="onRowClick('edit',row)">编辑</el-button>
             </template>
             <template v-else>
               <div>{{row[field.prop]}}</div>
@@ -110,7 +111,7 @@
 <!--      <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>-->
     </el-card>
   </div>
-<!--  <StationDialog ref="stationDialogRef" @refresh="loadData(true)"/>-->
+  <StationDialog ref="stationDialogRef" @refresh="loadData(true)"/>
 </template>
 
 <script setup lang="ts" name="StationList">
@@ -123,8 +124,9 @@ import ExtPage from '/@/components/form/ExtPage.vue'
 import mittBus from '/@/utils/mitt';
 import ExtDLabel from "/@/components/form/ExtDLabel.vue";
 import {useRouter} from "vue-router";
+import ExtImage from "/@/components/form/ExtImage.vue";
 const router = useRouter();
-// const StationDialog = defineAsyncComponent(() => import("/@/views/page/StationDialog.vue"));
+const StationDialog = defineAsyncComponent(() => import("/@/views/admin/station/list/dialog.vue"));
 
 //定义引用
 const queryRef = ref();
@@ -147,6 +149,7 @@ const state = reactive({
       // {label: 'en+运营商id', prop: 'operatorId', resizable: true},
       // {label: '所属运营平台', prop: 'equipmentOwnerId', width:160,resizable: true,fixed:'left'},
       {label: '站点名称', prop: 'stationName',width:160, resizable: true},
+      {label: '站点照片', prop: 'pictures',width:120, resizable: true},
       // {label: '充电中国家代码:CN', prop: 'countryCode', resizable: true},
       // {label: '充电站省市辖区编码', prop: 'areaCode', resizable: true},
       {label: '地址', prop: 'address',width:160, resizable: true},
@@ -244,7 +247,7 @@ const loadData = (refresh: boolean = false) => {
 
 // 打开修改用户弹窗
 const onRowClick = (type: string, row: any) => {
-  stationDialogRef.value.open(type, row);
+  stationDialogRef.value?.open(type, row);
 };
 
 // 删除用户

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

@@ -26,7 +26,7 @@ public class ActivityController {
     }
 
     @SysLog(value = "新增活动", ignoreParams = true)
-    @PostMapping
+    @PostMapping("add")
     R<?> createActivity(@RequestBody ActivityVo activityVo) {
         activityService.createActivity(activityVo);
         return R.success();

+ 5 - 5
admin/src/main/java/com/kym/admin/controller/BannerController.java

@@ -26,26 +26,26 @@ public class BannerController {
     }
 
     @SysLog("新增Banner")
-    @PostMapping
+    @PostMapping("add")
     R<?> createBanner(@RequestBody Banner banner) {
         return R.success(bannerService.createBanner(banner));
     }
 
     @SysLog("修改Banner")
-    @PutMapping
+    @PostMapping("modify")
     R<?> updateBanner(@RequestBody Banner banner) {
         return R.success(bannerService.updateBanner(banner));
     }
 
     @SysLog("Banner列表")
-    @GetMapping
+    @PostMapping("list")
     R<?> listBanner(@RequestBody BannerQueryParam params) {
         return R.success(bannerService.listBanner(params));
     }
 
     @SysLog("查询单个Banner")
-    @PostMapping("/{bannerId}")
-    R<?> listBanner(@PathVariable("bannerId") String bannerId) {
+    @GetMapping("/{bannerId}")
+    R<?> getBannerById(@PathVariable("bannerId") String bannerId) {
         return R.success(bannerService.getById(bannerId));
     }
 

+ 47 - 0
admin/src/main/java/com/kym/admin/controller/InvoiceController.java

@@ -0,0 +1,47 @@
+package com.kym.admin.controller;
+
+import com.kym.common.R;
+import com.kym.entity.admin.queryParams.InvoiceQueryParam;
+import com.kym.service.miniapp.InvoiceService;
+import com.kym.service.wechat.WxPayService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * <p>
+ * 发票记录表 前端控制器
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-15
+ */
+@RestController
+@RequestMapping("/invoice")
+public class InvoiceController {
+
+    private final InvoiceService invoiceService;
+
+    private final WxPayService wxPayService;
+
+    public InvoiceController(InvoiceService invoiceService, WxPayService wxPayService) {
+        this.invoiceService = invoiceService;
+        this.wxPayService = wxPayService;
+    }
+
+    /**
+     * 发票列表
+     *
+     * @param params
+     * @return 发票列表
+     */
+    @PostMapping("/list")
+    R<?> list(@RequestBody InvoiceQueryParam params) {
+        return R.success(invoiceService.listInvoice(params));
+    }
+
+
+
+}

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

@@ -56,7 +56,7 @@ public class StationController {
     }
 
     @SysLog("修改站点信息")
-    @PutMapping()
+    @PostMapping("modify")
     R<?> modifyStation(@RequestBody Station station){
         stationService.modifyStation(station);
         return R.success();

+ 5 - 2
entity/src/main/java/com/kym/entity/admin/Banner.java

@@ -1,13 +1,14 @@
 package com.kym.entity.admin;
 
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.kym.entity.BaseEntity;
-import java.io.Serializable;
-import java.time.LocalDateTime;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.experimental.Accessors;
 
+import java.time.LocalDateTime;
+
 /**
  * <p>
  * banner配置表
@@ -47,11 +48,13 @@ public class Banner extends BaseEntity {
     /**
      * 开始时间
      */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime startTime;
 
     /**
      * 结束时间
      */
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime endTime;
 
     /**

+ 50 - 0
entity/src/main/java/com/kym/entity/admin/queryParams/InvoiceQueryParam.java

@@ -0,0 +1,50 @@
+package com.kym.entity.admin.queryParams;
+
+import lombok.Data;
+
+
+/**
+ * @author skyline
+ * @description 运营后台发票查询参数
+ * @date 2023-08-22 18:56
+ */
+@Data
+public class InvoiceQueryParam extends PageParams {
+    /**
+     * id
+     */
+    private Long id;
+    /**
+     * 发票抬头
+     */
+    private String invoiceTitle;
+    /**
+     * 发票状态
+     */
+    private Integer status;
+
+    /**
+     * 开票人
+     */
+    private String biller;
+
+    /**
+     * 公司税号
+     */
+    private String taxId;
+
+    /**
+     * 发票类型:INDIVIDUAL-个人 ORGANIZATION-企业
+     */
+    private String invoiceType;
+
+    /**
+     * 接收发票邮箱
+     */
+    private String email;
+
+    /**
+     * 电话
+     */
+    private String phone;
+}

+ 14 - 0
miniapp/src/main/java/com/kym/miniapp/controller/InvoiceController.java

@@ -1,6 +1,7 @@
 package com.kym.miniapp.controller;
 
 import com.kym.common.R;
+import com.kym.entity.admin.queryParams.InvoiceQueryParam;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.wechat.WxPayService;
@@ -60,6 +61,19 @@ public class InvoiceController {
         return R.success(invoiceService.applyInvoice(params));
     }
 
+
+    /**
+     * 发票列表
+     *
+     * @param params
+     * @return 发票列表
+     */
+    @GetMapping("/list")
+    R<?> list(@RequestBody InvoiceQueryParam params) {
+        return R.success(invoiceService.listInvoice(params));
+    }
+
+
     /**
      * 接收发票相关通知
      *

+ 6 - 2
service/src/main/java/com/kym/service/admin/impl/ActivityServiceImpl.java

@@ -30,6 +30,7 @@ import java.time.LocalTime;
 import static com.kym.entity.admin.Activity.APPLY_STATION_部分站点;
 import static com.kym.entity.admin.Activity.DISCOUNT_TYPE_服务费折扣权益;
 
+
 /**
  * <p>
  * 服务实现类
@@ -123,8 +124,11 @@ public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> i
             var rechargeRightsList = rechargeRightsService.lambdaQuery().eq(RechargeRights::getActivityId, activityId).list();
             activityVo.setRechargeRightsList(rechargeRightsList);
             var activityStationIds = activityStationService.lambdaQuery().eq(ActivityStation::getActivityId, activityId).list().stream().map(ActivityStation::getStationId).toList();
-            var stationList = stationService.lambdaQuery().in(Station::getStationId, activityStationIds).list();
-            activityVo.setStationList(stationList);
+            if (CommUtil.isNotEmptyAndNull(activityStationIds)) {
+                activityVo.setStationIds(activityStationIds);
+                var stationList = stationService.lambdaQuery().in(Station::getStationId, activityStationIds).list();
+                activityVo.setStationList(stationList);
+            }
         }
         return activityVo;
     }

+ 1 - 1
service/src/main/java/com/kym/service/admin/impl/BannerServiceImpl.java

@@ -45,7 +45,7 @@ public class BannerServiceImpl extends ServiceImpl<BannerMapper, Banner> impleme
                 .set(banner.getEndTime() != null, Banner::getEndTime, banner.getEndTime())
                 .set(banner.getStatus() != null, Banner::getStatus, banner.getStatus())
                 .set(!CommUtil.isEmptyOrNull(banner.getRemark()), Banner::getRemark, banner.getRemark())
-                .eq(Banner::getId, banner.getId());
+                .eq(Banner::getId, banner.getId()).update();
         return banner;
     }
 

+ 3 - 1
service/src/main/java/com/kym/service/miniapp/InvoiceService.java

@@ -1,10 +1,11 @@
 package com.kym.service.miniapp;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.kym.entity.admin.queryParams.InvoiceQueryParam;
+import com.kym.entity.common.PageBean;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
 import com.kym.entity.wechat.TitleUrl;
-import jakarta.servlet.http.HttpServletRequest;
 
 /**
  * <p>
@@ -18,4 +19,5 @@ public interface InvoiceService extends IService<Invoice> {
 
     TitleUrl applyInvoice(ApplyInvoiceParams params);
 
+    PageBean<Invoice> listInvoice(InvoiceQueryParam params);
 }

+ 19 - 1
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -3,8 +3,12 @@ package com.kym.service.miniapp.impl;
 import cn.dev33.satoken.stp.StpUtil;
 import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.pagehelper.PageHelper;
 import com.kym.common.exception.BusinessException;
+import com.kym.common.utils.CommUtil;
 import com.kym.common.utils.OrderUtils;
+import com.kym.entity.admin.queryParams.InvoiceQueryParam;
+import com.kym.entity.common.PageBean;
 import com.kym.entity.miniapp.ChargeOrder;
 import com.kym.entity.miniapp.Invoice;
 import com.kym.entity.miniapp.params.ApplyInvoiceParams;
@@ -14,7 +18,6 @@ import com.kym.mapper.miniapp.InvoiceMapper;
 import com.kym.service.miniapp.ChargeOrderService;
 import com.kym.service.miniapp.InvoiceService;
 import com.kym.service.wechat.WxPayService;
-import jakarta.servlet.http.HttpServletRequest;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -98,4 +101,19 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
 
     }
 
+    @Override
+    public PageBean<Invoice> listInvoice(InvoiceQueryParam params) {
+        PageHelper.startPage(params.getPageNum(), params.getPageSize());
+        var list = lambdaQuery()
+                .like(CommUtil.isNotEmptyAndNull(params.getInvoiceTitle()), Invoice::getInvoiceTitle, params.getInvoiceTitle())
+                .like(CommUtil.isNotEmptyAndNull(params.getBiller()), Invoice::getBiller, params.getBiller())
+                .like(CommUtil.isNotEmptyAndNull(params.getTaxId()), Invoice::getTaxId, params.getTaxId())
+                .like(CommUtil.isNotEmptyAndNull(params.getInvoiceType()), Invoice::getInvoiceType, params.getInvoiceType())
+                .like(CommUtil.isNotEmptyAndNull(params.getEmail()), Invoice::getEmail, params.getEmail())
+                .like(CommUtil.isNotEmptyAndNull(params.getPhone()), Invoice::getPhone, params.getPhone())
+                .eq(params.getStatus() != null, Invoice::getStatus, params.getStatus())
+                .list();
+        return new PageBean<>(list);
+    }
+
 }