Selaa lähdekoodia

优惠券相关

zuy 2 vuotta sitten
vanhempi
säilyke
089c0cb9c4

+ 4 - 4
admin-web/src/components/form/ExtDLabel.vue

@@ -1,7 +1,7 @@
 <style scoped lang="scss">
 .status-label {
   display: inline-block;
-  padding: 3px 6px;
+  padding: 5px 6px 1px 6px;
   font-size: 11px;
   font-weight: 600;
   line-height: 1;
@@ -9,7 +9,7 @@
   text-align: center;
   white-space: nowrap;
   vertical-align: baseline;
-  border-radius: 10px;
+  border-radius: 2px;
   margin-top: 3px;
   width: auto;
   color: #fff;
@@ -130,8 +130,8 @@ const setupColorStyle = (hex: string = "#000000") => {
   let hexToRgb = u.hexToRgb(hex);
   let {r, g, b} = hexToRgb;
   return {
-    'text-shadow': `2px 2px 3px rgba(${r},${g},${b},0.2)`,
-    // 'background-color': `rgba(${r},${g},${b},0.2)`,
+    // 'text-shadow': `2px 2px 3px rgba(${r},${g},${b},0.2)`,
+    'background-color': `rgba(${r},${g},${b},0.2)`,
     'color': `rgb(${r},${g},${b})`
   }
 }

+ 1 - 0
admin-web/src/components/form/ExtPage.vue

@@ -42,6 +42,7 @@ const state = reactive({
 watch(
     () => props.value,
     (val, oldVal) => {
+      console.log("page",val)
       nextTick(() => {
         setupPage();
       })

+ 269 - 0
admin-web/src/components/form/ExtQueryForm.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="dynamic-state.form-container">
+    <el-card shadow="hover">
+      <el-form
+          :model="state.form"
+          :rules="rules"
+          ref="formRulesOneRef"
+          size="default" label-width="0px" class="mt5">
+        <el-row :gutter="15">
+          <el-col
+              :xs="field.xs"
+              :sm="field.sm"
+              :md="field.md"
+              :lg="field.md"
+              :xl="field.xl"
+              class="mb20"
+              v-for="(field, key) in state.cols"
+              :key="key">
+            <el-input
+                v-if="field.type === 'text'"
+                v-model="state.form[field.prop]"
+                :placeholder="field.placeholder"
+                clearable
+                @blur="onQueryChange"
+                style="width: 100%">
+            </el-input>
+            <el-input
+                v-if="field.type === 'number'"
+                type="number"
+                v-model="state.form[field.prop]"
+                :placeholder="field.placeholder"
+                clearable
+                @blur="onQueryChange"
+                style="width: 100%">
+            </el-input>
+            <el-date-picker
+                v-else-if="field.type === 'datetime'"
+                v-model="state.form[field.prop]"
+                type="datetime"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @change="onQueryChange">
+            </el-date-picker>
+            <ext-date-picker
+                v-else-if="field.type === 'year'"
+                v-model="state.form[field.prop]"
+                type="year"
+                format="YYYY"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-date-picker>
+            <el-date-picker
+                v-else-if="field.type === 'month'"
+                v-model="state.form[field.prop]"
+                type="month"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @change="onQueryChange">
+            </el-date-picker>
+            <el-date-picker
+                v-else-if="field.type === 'date'"
+                v-model="state.form[field.prop]"
+                type="date"
+                :value-format="field?.conf?.vfmt"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @change="onQueryChange">
+            </el-date-picker>
+            <el-time-picker
+                v-else-if="field.type === 'time'"
+                v-model="state.form[field.prop]"
+                type="time"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @change="onQueryChange">
+            </el-time-picker>
+            <ext-d-select
+                v-else-if="field.type === 'dict'"
+                v-model="state.form[field.prop]"
+                :type="field.conf?.dict"
+                :data-range="field.conf?.dataRange"
+                :placeholder="field.placeholder"
+                :multiple="field.conf?.multiple"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-d-select>
+            <ext-select
+                v-else-if="field.type === 'select'"
+                v-model="state.form[field.prop]"
+                :url="field.conf?.url"
+                :query="field.conf?.query"
+                :multiple="field.conf?.multiple"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-select>
+            <ext-search
+                v-else-if="field.type === 'search'"
+                v-model="state.form[field.prop]"
+                :multiple="field.conf?.multiple"
+                :placeholder="field.placeholder"
+                :config="field.conf"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-search>
+
+            <ext-tree-select
+                v-else-if="field.type === 'dept'"
+                v-model="state.form[field.prop]"
+                :multiple="field.conf?.multiple"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-tree-select>
+
+            <ext-tree-select
+                v-else-if="field.type === 'user'"
+                v-model="state.form[field.prop]"
+                mode="user"
+                :multiple="field.conf?.multiple"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                @on-change="onQueryChange">
+            </ext-tree-select>
+
+            <ext-boolean
+                v-else-if="field.type === 'bool'"
+                v-model="state.form[field.prop]"
+                :placeholder="field.placeholder"
+                style="width: 100%"
+                :disabled="field.disabled"
+                @on-change="onQueryChange">
+            </ext-boolean>
+
+          </el-col>
+          <slot name="extQuery"></slot>
+        </el-row>
+      </el-form>
+      <el-row class="flex-warp mt5">
+        <div>
+          <el-button-group>
+            <el-button plain size="default" type="primary" @click="onResetForm(formRulesOneRef)">
+              <SvgIcon name="ele-RefreshRight"/>
+              重置
+            </el-button>
+            <el-button plain size="default" type="primary" @click="onQueryChange">
+              <SvgIcon name="ele-Search"/>
+              查询
+            </el-button>
+
+            <slot name="extraLeft"></slot>
+          </el-button-group>
+        </div>
+        <div class="flex flex-auto" style="justify-content: flex-end">
+          <slot name="extraRight"></slot>
+        </div>
+      </el-row>
+
+      <ext-import
+          @confirm="handleImport"
+          ref="import_ref"
+          v-if="state.importEnable"
+          :importUrl="importConfig.url"
+          :templateUrl="importConfig.template"
+          v-auths="importConfig.auths">
+      </ext-import>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts" name="ExtForm">
+import {computed, defineAsyncComponent, onMounted, ref} from 'vue';
+import type {FormInstance} from 'element-plus';
+import fieldUtil from "/@/utils/field";
+import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtSelect from "/@/components/form/ExtSelect.vue";
+import ExtTreeSelect from "/@/components/form/ExtTreeSelect.vue";
+import ExtSearch from "/@/components/form/ExtSearch.vue";
+import {Msg} from "/@/utils/message";
+import u from "/@/utils/u";
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+
+const ExtBoolean = defineAsyncComponent(() => import('/@/components/form/ExtBoolean.vue'))
+// import ExtImport from "/@/components/form/ExtImport.vue";
+
+const emit = defineEmits(['on-change', 'update:modelValue']);
+const props = defineProps({
+  columns: {
+    type: Array < IField >,
+    require: true,
+    default: () => []
+  },
+  modelValue: {
+    type: Object,
+    require: true
+  },
+  importConfig: {
+    type: Object,
+    default: () => {
+      return {url: '', auths: [], template: ''}
+    }
+  },
+  exportConfig: {
+    type: Object
+  },
+  pageQuery: {
+    type: Object
+  }
+})
+
+
+const rules = computed(() => {
+  return [];
+})
+
+// 定义变量内容
+const import_ref = ref();
+const formRulesOneRef = ref<FormInstance>();
+const state = ref({
+  cols: [] as Array<any>,
+  form: {},
+  importEnable: false,
+  exportEnable: false,
+});
+
+onMounted(() => {
+  if (props.importConfig && props.importConfig.url) {
+    state.value.importEnable = true;
+  }
+  if (props.exportConfig && props.exportConfig.url) {
+    state.value.exportEnable = true;
+  }
+
+  console.log(props.columns)
+  state.value.cols = fieldUtil.toFormQueryField(props.columns);
+  console.log(state.value.cols)
+  let val = {...props.modelValue}
+  state.value.cols.forEach((col: any) => {
+    val[col.prop] = null;
+  })
+  console.log(val)
+  state.value.form = val;
+})
+
+const onQueryChange = () => {
+  console.log("onQueryChange>>>", JSON.stringify(state.value.form))
+  emit("update:modelValue", state.value.form);
+  emit("on-change")
+}
+
+// 重置表单
+const onResetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  formEl.resetFields();
+  props.columns.forEach(key => {
+    if (key.type == 'datetime' || key.type == 'date' || key.type == 'daterange') {
+      state.value.form[key.prop + 'Start'] = null
+      state.value.form[key.prop + 'End'] = null
+    }
+    state.value.form[key.prop] = null
+  })
+  // console.log(state.value.form,props.columns)
+  emit("update:modelValue", {})
+  emit("on-change")
+};
+</script>

+ 50 - 28
admin-web/src/components/form/ExtSContainer.vue

@@ -50,8 +50,8 @@
 
     <template #footer>
       <span class="dialog-footer">
-        <el-button @click="state.show = false">取消</el-button>
-        <el-button type="primary" @click="confirm">确定</el-button>
+        <el-button @click="state.show = false" size="default">取消</el-button>
+        <el-button type="primary" @click="confirm" size="default">确定</el-button>
       </span>
     </template>
   </el-drawer>
@@ -59,26 +59,41 @@
 
 
 <script setup lang="ts" name="ExtSContainer">
-import {reactive, onMounted, defineAsyncComponent,nextTick,ref} from 'vue';
+import {defineAsyncComponent, nextTick, onMounted, reactive, ref} from 'vue';
 
-import {$body, $post} from "/@/utils/request";
+import {$body, $get} from "/@/utils/request";
 import u from "/@/utils/u";
 import ExtQueryForm from "/@/components/form/ExtQueryForm.vue";
 
 const ExtPage = defineAsyncComponent(() => import('/@/components/form/ExtPage.vue'))
 const ExtTable = defineAsyncComponent(() => import('/@/components/form/ExtTable.vue'))
 
-const  queryRef = ref();
+const queryRef = ref();
+
+const props = defineProps({
+  listKey: {
+    type: String,
+    default: 'list'
+  },
+  countKey: {
+    type: String,
+    default: 'total'
+  },
+  idKey:{
+    type:String,
+    default:'id'
+  }
+})
+
 const state = reactive({
   queryItem: {
     id: undefined,
   },
   init: false,
-  columns: [
-    {label: 'ID', prop: 'id', width: 70,query: true,type:'number',conf:{op:'eq'}},
-    {label: '名称', prop: 'name', query: true, type: 'text', resizable: true},
-    {label: '创建时间', prop: 'createAt', query: true,sortable: 'custom',type:'datetime',hide:true},
-    {label: '更新时间', prop: 'updateAt',query: true, sortable: 'custom',type:'datetime',hide:true},
+  columns: [],
+  initColumns: [
+    {label: 'ID', prop: 'userId', width: 170, query: false, type: 'number', conf: {op: 'eq'}, resizable: true},
+    {label: '名称', prop: 'userName', width: 120,query: false, type: 'text', resizable: true},
   ],
   datas: [],
   checkIds: [],
@@ -96,8 +111,8 @@ const state = reactive({
   dataList: [],
   checkDataList: [],
   multiple: false,
-  height:500,
-  loading:false
+  height: 500,
+  loading: false
 })
 
 onMounted(() => {
@@ -109,7 +124,6 @@ onMounted(() => {
 })
 
 
-
 const open = (callback: Function, multiple: boolean = false, config: any, chosenIdList: Array<number>) => {
   state.config = config;
   state.show = true;
@@ -134,21 +148,29 @@ const loadData = (refresh: boolean = false) => {
   if (refresh) {
     state.pageQuery.pageIndex = 1;
   }
-  let {url, domain, cols, query} = state.config;
-  // if (url) {
-  //   request = u.fmt.fmtUrl(url);
-  // } else {
-  //   request = u.fmt.fmtUrl(`/${domain}/list`);
-  // }
-  let requestQuery = {...state.pageQuery,...query, ...state.queryForm}
+  let {url, domain, cols, query, method} = state.config;
+  let requestQuery = {...state.pageQuery, ...query, ...state.queryForm}
 
   console.log(requestQuery)
-  $body(url, requestQuery).then((res: any) => {
-    state.loading = false;
-    let {list, count} = res;
-    state.pageQuery.total = count;
-    state.dataList = list;
-  })
+  switch (method) {
+    case 'get':
+      $get(url, requestQuery).then((res: any) => {
+        state.loading = false;
+        state.pageQuery.total = res[props.countKey]
+        state.dataList = props.listKey ? res[props.listKey] : res;
+      })
+      break;
+    case 'body':
+    default:
+      $body(url, requestQuery).then((res: any) => {
+        state.loading = false;
+        let {list, count} = res;
+        state.pageQuery.total = res[props.countKey]
+        state.dataList = props.listKey ? res[props.listKey] : res;
+      })
+      break;
+  }
+
 }
 
 
@@ -178,7 +200,8 @@ const initQuery = () => {
   }
   let {columns, query} = state.config;
   if (!u.isEmptyOrNull(columns)) {
-//
+    state.columns = state.initColumns.concat(columns);
+    console.log(state.columns)
   }
   console.log(state.config)
   if (query) {
@@ -197,7 +220,6 @@ const initQuery = () => {
 }
 
 
-
 // 暴露变量
 defineExpose({
   open,

+ 0 - 2
admin-web/src/components/form/ExtTable.vue

@@ -59,7 +59,6 @@
 
           <template v-else>
             <template v-if="field.render">
-              <ExtRender :func="field.render" :data="row"/>
             </template>
             <template v-else-if="field.type==='rich'">
               <p v-html="row[field.prop]"></p>
@@ -125,7 +124,6 @@
 import {reactive, onMounted,defineAsyncComponent} from 'vue';
 import fieldUtil from "/@/utils/field";
 import ExtDLabel from "/@/components/form/ExtDLabel.vue";
-import ExtRender from "/@/components/form/ExtRender.vue";
 import ExtBoolean from "/@/components/form/ExtBoolean.vue";
 import u from "/@/utils/u";
 

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

@@ -227,8 +227,9 @@ export const adminRoutes: Array<RouteRecordRaw> = [
             },
             {
                 path: '/activity',
-                name: 'adminActivity',
-                component: () => import('/@/views/admin/activity/index.vue'),
+                name: 'activity',
+                component: () => import('/@/layout/routerView/parent.vue'),
+                redirect: '/activity/list',
                 meta: {
                     title: '营销活动',
                     isLink: '',
@@ -236,10 +237,58 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isKeepAlive: true,
                     isAffix: false,
                     isIframe: false,
-                    icon: 'ele-PieChart',
-                    perm:"activity.list",
-                }
+                    icon: 'ele-Tools',
+                    perm: "activity.list,coupon.list",
+                },
+                children: [
+                    {
+                        path: '/activity/list',
+                        name: 'adminActivity',
+                        component: () => import('/@/views/admin/activity/index.vue'),
+                        meta: {
+                            title: '活动列表',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            icon: 'ele-PieChart',
+                            perm:"activity.list",
+                        }
+                    },
+                    {
+                        path: '/activity/coupon',
+                        name: 'adminCoupon',
+                        component: () => import('/@/views/admin/activity/coupon/index.vue'),
+                        meta: {
+                            title: '优惠券列表',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            icon: 'ele-PieChart',
+                            // perm:"coupon.list",
+                        }
+                    },
+                    {
+                        path: '/activity/userCoupon',
+                        name: 'adminUserCoupon',
+                        component: () => import('/@/views/admin/activity/userCoupon/index.vue'),
+                        meta: {
+                            title: '用户优惠券',
+                            isLink: '',
+                            isHide: false,
+                            isKeepAlive: true,
+                            isAffix: false,
+                            isIframe: false,
+                            icon: 'ele-PieChart',
+                            // perm:"coupon.list",
+                        }
+                    },
+                ]
             },
+
             {
                 path: '/ordering',
                 name: 'adminOrdering',

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

@@ -476,3 +476,52 @@ body,
 .scroll_y {
   overflow-y: scroll;
 }
+
+
+.sub-group-left {
+  display: inline-block;
+  position: relative;
+  margin: 15px 0;
+  font-weight: 600;
+  font-size: 14px;
+  width: 100%;
+
+  &:before {
+    content: " ";
+    border-left: 4px solid var(--el-color-primary);
+    height: 10px;
+    display: block;
+    /* margin-right: 6px; */
+    position: absolute;
+    left: -7px;
+    top: 5px;
+    border-radius: 2px;
+  }
+}
+
+
+.sub-group-bottom {
+  display: inline-block;
+  position: relative;
+  margin: 15px 0;
+  font-weight: 600;
+  font-size: 14px;
+  width: 100%;
+
+  &:before {
+    content: " ";
+    border-bottom: 4px solid var(--el-color-primary);
+    position: absolute;
+    bottom: -4px;
+    width: 20px;
+    border-radius: 2px;
+  }
+
+  &:hover {
+    &:before {
+      transition: width 0.5s;
+      width: 30px;
+    }
+  }
+}
+

+ 4 - 3
admin-web/src/utils/message.ts

@@ -15,7 +15,8 @@ export const Msg = {
             type: type
         })
     },
-    confirm(message: string, title: string = '提示', options?: any): Promise<any> {
+    confirm(message: string, title: string = '提示', options?: any,type='warning'): Promise<any> {
+        // @ts-ignore
         return ElMessageBox.confirm(
             message,
             title,
@@ -23,7 +24,7 @@ export const Msg = {
                 distinguishCancelAndClose: true,
                 confirmButtonText: options?.ok || '确认',
                 cancelButtonText: options?.cancel || '取消',
-                type: 'warning',
+                type: type,
                 buttonSize:'default'
             }
         ).then((v) => {
@@ -31,7 +32,7 @@ export const Msg = {
             return Promise.resolve();
         }).catch(e => {
             console.error(e)
-            return Promise.reject();
+            return Promise.reject(e);
         })
     },
     prompt(message: string, title: string = '提醒', options?: any): Promise<any> {

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

@@ -0,0 +1,490 @@
+<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">
+
+        <div class="sub-group-bottom"> 基本信息</div>
+        <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="remark" class="w100">
+          <el-input
+              maxlength="500"
+              show-word-limit
+              type="textarea"
+              :rows="3"
+              v-model.trim="state.ruleForm.activityDesc"
+              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>
+<!--        RechargeRights  "Coupon"-->
+        <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" v-if="state.ruleForm.discountType==='RechargeRights'">
+          <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>
+
+        <div class="sub-group-bottom"> 关联站点</div>
+<!--        <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"
+              data-key=""
+              clearable
+              class="w100 mt5">
+          </ext-select>
+        </el-form-item>
+
+        <div class="sub-group-bottom"> 关联权益</div>
+<!--        <el-divider content-position="left">关联权益</el-divider>-->
+        <el-button size="small" plain type="success" class="ml10" @click="handleAddRightsItem">
+          <SvgIcon name="ele-FolderAdd"/>
+          新增权益
+        </el-button>
+
+<!--        服务费折扣权益 -->
+        <template v-if="state.ruleForm.discountType==='RechargeRights'">
+          <el-card v-for="(rights,idx) in state.ruleForm.rechargeRightsList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveRightsItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+            <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+              <el-input
+                  maxlength="500"
+                  show-word-limit
+                  type="textarea"
+                  :rows="2"
+                  v-model.trim="rights.rightsDesc"
+                  placeholder="权益描述"
+                  clearable
+                  class="w100">
+              </el-input>
+            </el-form-item>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="最小充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMin" :ratio="100"  placeholder="最小充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMin"
+                      placeholder="最小充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="最大充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMax" :ratio="100"  placeholder="最大充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMax"
+                      placeholder="最大充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
+
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣" prop="discount">
+                  <el-input-number
+                      :controls="false"
+                      v-model.trim="rights.discount"
+                      placeholder="折扣:100代表无折扣,75代表75折"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="rights.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+          </el-card>
+        </template>
+
+<!--        优惠券-->
+        <template v-else>
+          <el-card v-for="(coupon,idx) in state.ruleForm.couponList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveCouponItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+
+
+              <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+                <el-input
+                    maxlength="500"
+                    show-word-limit
+                    type="textarea"
+                    :rows="2"
+                    v-model.trim="coupon.couponDesc"
+                    placeholder="权益描述"
+                    clearable
+                    class="w100">
+                </el-input>
+              </el-form-item>
+
+
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣类型" prop="couponType">
+                  <ext-d-select
+                      @on-change="handleStationChange"
+                      v-model="coupon.couponType"
+                      placeholder="折扣类型"
+                      type="Activity.couponType"
+                      clearable
+                      class="wd200 ">
+                  </ext-d-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="coupon.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+
+            <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="最小服务费金额(元)" prop="minServiceMoney">
+                    <ext-input-number   class="wd200 "  v-model="coupon.minServiceMoney" :ratio="100"  placeholder="最小服务费金额"></ext-input-number>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="折扣" prop="discount">
+                    <el-input-number
+                        :controls="false"
+                        v-model.trim="coupon.discount"
+                        placeholder="折扣:100代表无折扣,75代表75折;折扣金额(分)"
+                        clearable
+                        class="wd200">
+                    </el-input-number>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+
+
+          </el-card>
+        </template>
+
+
+      </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";
+import ExtInputNumber from "/@/components/form/ExtInputNumber.vue";
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = () => ({
+  action: '',
+  ruleForm: {
+    id: 0,
+    rechargeRightsList: [] as Array<any>,
+    couponList: [] as Array<any>,
+  },
+  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 = () => {
+  let rechargeRightsList = state.ruleForm.rechargeRightsList;
+  if (!u.isEmptyOrNull(rechargeRightsList)) {
+    for (let i = 0; i < rechargeRightsList.length; i++) {
+      const item: any = rechargeRightsList[i];
+      if (item.amountMax <=0) {
+        Msg.message(`权益最大充值金额不能小于0`, 'error')
+        return false;
+      }
+
+      if (item.amountMin > item.amountMax) {
+        Msg.message(`权益最小充值金额不能大于最大充值金额`, 'error')
+        return false;
+      }
+    }
+
+/*    state.ruleForm.rechargeRightsList.forEach((rights: any) => {
+      rights.amountMin = rights.amountMin * 100;
+      rights.amountMax = rights.amountMax * 100;
+    })*/
+  }
+
+  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) => {
+    if (res && res.rechargeRightsList) {
+/*      res.rechargeRightsList.forEach((rights: any) => {
+        rights.amountMin = rights.amountMin / 100;
+        rights.amountMax = rights.amountMax / 100;
+      })*/
+    }
+    state.ruleForm = res;
+    state.ruleForm.rechargeRightsList = res.rechargeRightsList || []
+  })
+}
+
+const handleStationChange = (applyStation: number) => {
+  console.log(applyStation)
+  if (applyStation == 0) {
+    // state.ruleForm.stationIds = [];
+  }
+
+}
+
+const handleAddRightsItem = () => {
+  if(state.ruleForm.discountType==='RechargeRights'){
+    state.ruleForm.rechargeRightsList.unshift({
+      rightsDesc: '',
+      amountMin: 0,
+      amountMax: 0,
+      validity: 1
+    })
+  }else if(state.ruleForm.discountType==='Coupon'){
+    state.ruleForm.couponList.unshift({
+      couponDesc: '',
+      couponType: 'Discount',
+      minServiceMoney: 0,
+      validity: 1
+    })
+  }
+
+}
+
+const handleRemoveRightsItem = (idx: number) => {
+  state.ruleForm.rechargeRightsList.splice(idx, 1)
+}
+const handleRemoveCouponItem = (idx: number) => {
+  state.ruleForm.couponList.splice(idx, 1)
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

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

@@ -0,0 +1,324 @@
+<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.couponType"
+            placeholder="券种"
+            type="Activity.couponType"
+            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-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==='couponType'">
+              <ext-d-label type="Activity.couponType" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='minServiceMoney'">
+              {{u.fmt.fmtMoney(row.minServiceMoney)}}
+            </template>
+            <template v-else-if="field.prop==='allowStacke'">
+              {{row.allowStacke?'是':'否'}}
+            </template>
+            <template v-else-if="field.prop==='action'">
+<!--              <el-button v-auth="'coupon.list'" size="small" plain type="success" @click="onRowClick('view',row)">查看</el-button>-->
+              <el-button v-auth="'coupon.modify'"  size="small" plain type="danger" @click="onCouponDispatch(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)"/>
+  <ExtSContainer ref="userSelectDialogRef"></ExtSContainer>
+  <AccountMobileUpload ref="accountMobileUploadDialogRef" @on-import-finish="handleImportAccountPhone"></AccountMobileUpload>
+</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 ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtDatePicker from "/@/components/form/ExtDatePicker.vue";
+import ExtDLabel from "/@/components/form/ExtDLabel.vue";
+import u from "/@/utils/u";
+
+const ActivityDialog = defineAsyncComponent(() => import("/@/views/admin/activity/coupon/dialog.vue"));
+const ExtSContainer = defineAsyncComponent(() => import('/@/components/form/ExtSContainer.vue'));
+const AccountMobileUpload = defineAsyncComponent(() => import('./upload.vue'));
+
+//定义引用
+const queryRef = ref();
+const activityDialogRef = ref();
+const userSelectDialogRef = ref();
+const accountMobileUploadDialogRef = ref();
+
+//定义变量
+const state = reactive({
+  formQuery: {
+    discountType:'Coupon'
+  },
+  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: 'couponType', resizable: true, width: 130},
+      {label: '活动时间', prop: 'period', resizable: true, width: 350},
+      // {label: '结束时间', prop: 'endTime',  resizable: true, width: 150},
+      {label: '优惠券描述', prop: 'couponDesc', align: 'center', width: 330},
+      {label: '最小服务费金额(元)', prop: 'minServiceMoney', resizable: true, width: 180},
+      {label: '折扣', prop: 'discount', 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: 100, align: 'center', fixed: 'right',
+      }
+    ],
+  },
+  handleCoupon:null
+})
+
+
+// 监听双向绑定 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
+  })
+
+});
+
+onBeforeUnmount(() => {
+})
+
+
+//region 方法区
+// 初始化表格数据
+const loadData = (refresh: boolean = false) => {
+  if (refresh) {
+    state.pageQuery.pageNum = 1;
+  }
+  state.tableData.loading = true;
+  $get(`/coupon/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 handleRowClick = (event) => {
+  console.log(event)
+  activityDialogRef.value.open("view", event);
+};
+
+const onRowClick = (type: string, row: any) => {
+  activityDialogRef.value.open(type, row);
+};
+
+const onCouponDispatch = (row: any) => {
+  state.handleCoupon = row;
+  Msg.confirm(`请选择发券方式`,'操作',{'ok':'选择用户发放','cancel':'导入号码发放'},'').then(() => {
+    //选择用户
+    let config ={
+      columns:[
+        {label: '手机号', query: true,width: 120, prop: 'mobilePhone', resizable: true,type:'text'},
+        // {label: '余额',query: false, width: 80, prop: 'balance', resizable: true,type:'number'},
+        {label: '状态', query: true,width: 80, prop: 'status', align: 'center',type:'dict',conf:{dict:'User.status'}},
+      ],
+      query:{},
+      url:'custom/listUser',
+      method:'get'
+    }
+      userSelectDialogRef.value.open(handleChooseAccount,true,config,[]);
+  }).catch(e=>{
+    console.log(e)
+    if('close'==e)return;
+    //上传用户手机号
+    accountMobileUploadDialogRef.value?.open();
+  });
+};
+
+const handleChooseAccount = (accountList:Array<any>)=>{
+  console.log(accountList)
+  if(u.isEmptyOrNull(accountList)){
+    Msg.message('请选择用户','error')
+    return;
+  }
+  let params ={
+    userIds:accountList.map(k=>k.userId),
+    couponIds:[state.handleCoupon.id]
+  }
+  $body(`coupon/issueCoupons`,params).then(()=>{
+    Msg.message('发放成功')
+  }).catch(e=>{
+    Msg.message('发放失败','error')
+  })
+}
+
+const handleImportAccountPhone = (phoneList:Array<any>) => {
+  console.log(phoneList)
+  if(u.isEmptyOrNull(phoneList)||u.isEmptyOrNull(phoneList.dataList)){
+    Msg.message('请导入用户手机号','error')
+    return;
+  }
+  let params ={
+    phones:phoneList?.dataList?.map(k=>k.mobilePhone),
+    couponIds:[state.handleCoupon.id]
+  }
+  $body(`coupon/issueCoupons`,params).then(()=>{
+    Msg.message('发放成功')
+  }).catch(e=>{
+    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>

+ 473 - 0
admin-web/src/views/admin/activity/coupon/upload.vue

@@ -0,0 +1,473 @@
+<template>
+  <el-dialog
+      :title="'导入号码'"
+      v-model="state.dialog.visible"
+      width="75%"
+      destroy-on-close
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      draggable
+      class="pd0 dialog-padding-none"
+  >
+
+    <el-tabs class="demo-tabs">
+      <el-tab-pane>
+        <template #label>
+        <span class="custom-tabs-label">
+          <SvgIcon name="ele-Grid"></SvgIcon>
+          <span>Excel导入</span>
+        </span>
+        </template>
+
+        <el-button @click="handleOpenFile" plain type="success" class="mt5"> 请选择excel文件上传导入</el-button>
+        <el-text type="primary" class="ml5">{{ state.uploadFileName }} <SvgIcon v-if="state.uploadFileName" name="ele-Remove" @click="reset" color="var(--el-color-danger)"></SvgIcon></el-text>
+
+        <!--        <ext-upload
+                    v-model="state.excelForm.file"
+                    placeholder="请选择上传文件"
+                    tips="请选择excel文件上传导入"
+                    style="width: 100%"
+                    :limit="1"
+                    mime="excel"
+                    @on-success="handleUploadFileSuccess">
+                </ext-upload>-->
+
+      </el-tab-pane>
+
+    </el-tabs>
+
+
+
+    <div class="w100 mt5">
+      <div class="w100">
+        <el-scrollbar @scroll="handleFieldHeaderScroll" ref="fieldHeaderScrollRef" :min-size="0">
+          <div class="model-field-select-wrapper">
+            <div class="model-field-select-header" v-for="(field,idx) in state.excelForm.columns"
+                 :key="'select_'+idx">
+              <template v-if="idx===0">
+                <div style="width: 135px;height: 32px;line-height:32px;border:1px solid #eee;text-align: center;">导入列选择</div>
+              </template>
+              <el-select-v2 v-else
+                            v-model="state.excelForm.importFields[idx]"
+                            :options="state.excelForm.selectFields"
+                            style="width: 135px;height: 36px;border:none;display: inline-block;flex-shrink: 0;margin-right: 10px;"
+                            filterable
+                            clearable>
+              </el-select-v2>
+            </div>
+          </div>
+        </el-scrollbar>
+      </div>
+
+      <vxe-table
+          ref="excelRef"
+          border="full"
+          show-header-overflow
+          :show-footer="false"
+          :max-height="state.tableHeight"
+          :loading="false"
+          header-cell-class-name="task-list-header"
+          header-align="left"
+          empty-text=" "
+          @cell-click="handleCellClick"
+          :data="state.excelForm.data"
+          @scroll="handleDataTableScroll"
+          :row-config="{isHover:true,isCurrent:true}"
+          :tooltip-config="{zIndex:9999}"
+          :scroll-x="{enabled: true, gt: 100}"
+          :scroll-y="{enabled: true, gt: 100}">
+
+        <vxe-column
+            v-for="(field,idx) in state.excelForm.columns"
+            :key="idx"
+            :field="field.field"
+            :width="135"
+            :title="field.name"
+            show-overflow="title">
+          <template v-if="field.field==='idx'" #default="{row,rowIndex}">
+            <div class="w100  flex flex-align-items-center flex-justify-center  hover-parent">
+
+              <el-dropdown @command="handleRow($event,row,rowIndex)">
+                <el-text type="primary" class="hover-child ml5 cursor-pointer font12">        <span>{{ rowIndex+1}}</span></el-text>
+                <template #dropdown>
+                  <el-dropdown-item command="setHeader" style="color:var(--el-color-success)" class="font12">设为表头</el-dropdown-item>
+                  <el-dropdown-item command="delete" style="color:var(--el-color-danger)" class="font12">删除本行</el-dropdown-item>
+                </template>
+              </el-dropdown>
+            </div>
+          </template>
+        </vxe-column>
+      </vxe-table>
+
+    </div>
+
+    <template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onClose" size="default">取 消</el-button>
+					<el-button type="primary" @click="handleConfirm" :loading="state.importLoading" size="default">导入</el-button>
+				</span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts" name="EndpointImportDialog">
+import {reactive, onMounted, nextTick, ref, toRaw, unref} from 'vue';
+
+//定义初始变量,重置使用
+import {useFileDialog} from '@vueuse/core'
+
+import {$body, $post} from "/@/utils/request";
+import {Msg} from "/@/utils/message";
+import {VxeTable, VxeColumn, VxeTableInstance} from "vxe-table";
+import 'vxe-table/lib/style.css'
+import {useDebounceFn, useThrottleFn} from '@vueuse/core'
+
+import {ElScrollbar} from 'element-plus'
+import * as XLSX from 'xlsx'
+
+const fieldHeaderScrollRef = ref<InstanceType<typeof ElScrollbar>>();
+const excelRef = ref<VxeTableInstance>();
+
+
+const {files, open: handleOpenFile, reset, onChange:handleFileChange} = useFileDialog(
+    {
+      multiple: false,
+      accept: '.xls,.xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel'
+    })
+
+
+handleFileChange((files)=>{
+  parseExcel()
+})
+
+const initState = () => ({
+  dialog: {
+    visible: false,
+    type: '',
+    title: '',
+    submitTxt: '',
+  },
+  initColumnList: [{field: 'idx', name: '#', fixed: 'left'}],
+  excelForm: {
+    file: '',
+    rowHeaderIndex: 0,
+    rowContentIndex: 1,
+    columns: [] as any[],
+    sheet: '',
+    sheets: [] as string[],
+    data: [],
+    loading: false,
+    importFields: [] as string[],
+    selectFields: []
+  },
+  uploadFileName: '' as String | null,
+  importLoading: false,
+  tableHeight: 400,
+  fieldList:[
+    {label:'手机号',value:'mobilePhone'},
+  ]
+})
+
+// 定义变量内容
+const state = reactive(initState());
+
+const emit = defineEmits(['on-import-finish']);
+
+
+onMounted(() => {
+
+})
+
+const open = () => {
+  state.dialog.visible = true;
+
+  nextTick(() => {
+    let clientHeight = document.body.clientHeight;
+    let th = clientHeight - 60 - 120 - 300
+    state.tableHeight = th;
+  })
+  state.excelForm.selectFields = state.fieldList.map((k: any) => {
+    return {
+      value: k.value,
+      label: k.label
+    }
+  })
+}
+
+// 关闭弹窗
+const onClose = () => {
+  state.dialog.visible = false;
+  Object.assign(state, initState())
+  reset();
+};
+
+
+const handleCellClick = (e: any) => {
+  let {row, rowIndex, $rowIndex, column, columnIndex, $columnIndex} = e;
+  if (columnIndex === 0) {
+    return;
+  }
+  let val = state.excelForm.data[rowIndex][column.property]
+  Msg.prompt(`修改导入的数据`, '修改',
+      {
+        draggable: true,
+        type: 'primary',
+        value: val
+      }
+  ).then((res: any) => {
+    let {value, action} = res;
+    if (action === 'confirm') {
+      state.excelForm.data[rowIndex][column.property] = value
+    }
+  })
+}
+
+const handleConfirm = () => {
+  let checks: Array<string> = [];
+  let checkPass= true;
+  state.excelForm.importFields.forEach(key => {
+    if (key) {
+      if (checks.includes(key)) {
+        checkPass = false;
+        Msg.message('表头不可重复', 'error')
+        return false;
+      } else {
+        checks.push(key)
+      }
+    }
+  })
+  if(!checkPass)return;
+  debounceImport();
+}
+
+const debounceImport = useThrottleFn(() => {
+  state.importLoading = true;
+  let fieldIndexes: Array<number> = []
+  state.excelForm.importFields.forEach((key, idx) => {
+    if (key) {
+      fieldIndexes.push(idx - 1);
+    }
+  })
+let  fields=state.excelForm.importFields.filter(k => !!k);
+  let dataList : Array<any> = [];
+  state.excelForm.data.forEach(data => {
+    let item ={}
+    let arr: Array<any> = []
+    fieldIndexes.forEach((idx, i) => {
+      item[fields[i]] = data[`f${idx}`] || ""
+    })
+    dataList.push(item)
+  })
+
+  let params = {
+    fields: state.excelForm.importFields.filter(k => !!k),
+    fieldIndexes,
+    dataList
+  }
+
+  emit('on-import-finish',params)
+  state.dialog.visible = false;
+  state.importLoading = false;
+/*  $body(`station/importStation`, params).then(() => {
+    Msg.message(`导入成功`)
+    state.importLoading = false;
+    emit('on-import-finish')
+    state.dialog.visible = false;
+  }).catch(() => {
+    state.importLoading = false;
+  })*/
+}, 1500)
+
+//表头选择器滚动
+const handleFieldHeaderScroll = (ev: any) => {
+  let {scrollLeft, scrollTop} = ev;
+  // console.log(scrollLeft, scrollTop, ev)
+  if (scrollLeft >= 0) {
+    let st = excelRef.value.getScroll();
+    const el = excelRef.value?.$el.querySelector('.vxe-table--body-wrapper')
+    if (el) {
+      el.scrollLeft = scrollLeft
+    }
+  }
+}
+
+//数据表格滚动
+const handleDataTableScroll = (ev: any) => {
+  let {scrollLeft} = ev;
+  // console.log(type, scrollLeft, scrollHeight)
+  if (scrollLeft >= 0) {
+    fieldHeaderScrollRef.value?.setScrollLeft(scrollLeft);
+  }
+}
+
+const handleRow = (command: string, row: any, rowIndex: number) => {
+  if (command === 'setHeader') {
+    let tmpHeaders: any[] = []
+    let tmpHeader = state.excelForm.data[rowIndex]
+    Object.keys(tmpHeader).forEach((key, i) => {
+      if (key.startsWith("f")) {
+        let header = tmpHeader[key]
+        let find = state.fieldList.find((k: any) => k.name == header);
+        if (find) {
+          state.excelForm.importFields[i + 1] = find.label;
+        } else {
+          state.excelForm.importFields[i + 1] = "";
+        }
+        tmpHeaders.push({
+          field: `f${i}`,
+          name: header
+        });
+      }
+    })
+    state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
+    state.excelForm.data.splice(rowIndex, 1)
+    excelRef.value?.reloadData(state.excelForm.data);
+  } else if (command === 'delete') {
+    state.excelForm.data.splice(rowIndex, 1)
+    excelRef.value?.reloadData(state.excelForm.data);
+  }
+}
+
+const parseExcel = (sheetName: string = "") => {
+  // console.log(sheetName, state.excelForm.sheet,files)
+  // if(sheetName==state.excelForm.sheet&&state.excelForm.sheet){
+  //   return;
+  // }
+  if (!files || !files.value || !files.value[0]) {
+    state.excelForm.columns = []
+    excelRef.value?.reloadData([])
+    state.uploadFileName = null;
+    state.excelForm.sheets = []
+    return;
+  }
+  // console.log(files)
+  Msg.showLoading('解析中...')
+  state.uploadFileName = files.value[0].name;
+  let reader = new FileReader();
+  reader.onload = function () {
+    // console.log(reader)
+
+    let fileData = reader.result;
+    let wb = XLSX.read(fileData, {type: 'binary', cellDates: true});
+    state.excelForm.sheets = wb.SheetNames;
+    // {header:1} 取消标题列.
+    // console.log(wb.SheetNames)
+    let sheet = sheetName || wb.SheetNames[0];
+    state.excelForm.sheet = sheet;
+    let rowList: (any) = XLSX.utils.sheet_to_json(wb.Sheets[sheet], {header: 1});
+    // console.log(rowList)
+    let maxColumnLength = 1;
+    rowList.forEach((row: any, idx: number) => {
+      // console.log(row)
+      if (idx < 10) {
+        maxColumnLength = Math.max(maxColumnLength, row.length);
+      }
+    })
+
+    // console.log(maxColumnLength, rowList)
+    let tmpHeaders: Array<any> = []
+    let headers = rowList[state.excelForm.rowHeaderIndex];
+    for (let i = 0; i < maxColumnLength; i++) {
+      let header = i >= headers.length ? '' : headers[i];
+      let find = state.fieldList.find((k: any) => k.name == header);
+      if (find) {
+        state.excelForm.importFields[i + 1] = find.cname;
+      } else {
+        state.excelForm.importFields[i + 1] = "";
+      }
+      tmpHeaders.push({
+        field: `f${i}`,
+        name: header
+      });
+
+    }
+    state.excelForm.columns = state.initColumnList.concat(tmpHeaders)
+    // console.log(state.excelForm.columns)
+
+    let tmpContents: any[] = []
+    for (let i = state.excelForm.rowContentIndex; i < rowList.length; i++) {
+      let dl = rowList[i];
+      let rowItem = {}
+      dl.forEach((rowValue: any, columnIdx: number) => {
+        rowItem[`f${columnIdx}`] = rowValue
+      })
+      tmpContents.push(rowItem)
+    }
+    state.excelForm.data = tmpContents;
+    state.excelForm.loading = false;
+    excelRef.value?.reloadData(state.excelForm.data)
+    // data.exportTableData = rowObj;
+    Msg.hideLoading();
+  };
+  // 已二进制的形式读取文件
+  reader.readAsBinaryString(unref(files)[0]);
+  // 导入标识改为true
+  // data.exportSign = true;
+}
+
+const handleClick = (sheet: string) => {
+  parseExcel(sheet)
+  // handleRow("setHeader",state.excelForm.data[0],0)
+}
+
+/*
+onChange = ((files) => {
+  parseExcel();
+})
+*/
+
+
+defineExpose({
+  open
+})
+</script>
+
+<style scoped lang="scss">
+.custom-tabs-label {
+  display: inline-flex;
+  align-items: center;
+  align-content: center;
+  justify-content: space-between;
+}
+
+.model-field-select-wrapper {
+  width: 100%;
+  display: inline-flex;
+}
+
+.sheet-item {
+  width: 135px;
+  border-right: 2px solid #ddd;
+  border-bottom: 2px solid #ddd;
+  text-align: center;
+  cursor: pointer;
+}
+
+.model-field-select-header {
+  width: 135px;
+  height: 36px;
+  //overflow-x: auto;
+  //display: inline-flex;
+  //flex-wrap: wrap;
+
+  :deep(.el-select-v2__wrapper) {
+    border: 1px solid #eee !important;
+    border-radius: 0px !important;
+  }
+}
+
+:deep(.vxe-loading) {
+  display: none !important;
+}
+
+.pd0 {
+  :deep(.el-dialog__body) {
+    padding: 0 !important;
+  }
+}
+
+.vxe-table--tooltip-wrapper{
+  z-index: 10000 !important;
+}
+
+</style>

+ 207 - 85
admin-web/src/views/admin/activity/dialog.vue

@@ -20,6 +20,8 @@
           size="default"
           label-width="150px"
           class="mt5">
+
+        <div class="sub-group-bottom"> 基本信息</div>
         <el-form-item label="活动名称" prop="name" class="w100">
           <el-input
               v-model.trim="state.ruleForm.name"
@@ -58,6 +60,7 @@
               class="wd200">
           </ext-date-picker>
         </el-form-item>
+<!--        RechargeRights  "Coupon"-->
         <el-form-item label="优惠方式" prop="discountType">
           <ext-d-select
               v-model="state.ruleForm.discountType"
@@ -67,7 +70,7 @@
               class="wd200 ">
           </ext-d-select>
         </el-form-item>
-        <el-form-item label="目标用户" prop="targetUsers">
+        <el-form-item label="目标用户" prop="targetUsers" v-if="state.ruleForm.discountType==='RechargeRights'">
           <ext-d-select
               v-model="state.ruleForm.targetUsers"
               placeholder="目标用户"
@@ -117,7 +120,8 @@
           </el-input>
         </el-form-item>
 
-        <el-divider content-position="left">关联站点</el-divider>
+        <div class="sub-group-bottom"> 关联站点</div>
+<!--        <el-divider content-position="left">关联站点</el-divider>-->
         <el-form-item label="适用站点" prop="applyStation">
           <ext-d-select
               @on-change="handleStationChange"
@@ -142,88 +146,192 @@
           </ext-select>
         </el-form-item>
 
-
-        <el-divider content-position="left">关联权益</el-divider>
+        <div class="sub-group-bottom"> 关联权益</div>
+<!--        <el-divider content-position="left">关联权益</el-divider>-->
         <el-button size="small" plain type="success" class="ml10" @click="handleAddRightsItem">
           <SvgIcon name="ele-FolderAdd"/>
           新增权益
         </el-button>
 
-        <el-card v-for="(rights,idx) in state.ruleForm.rechargeRightsList" :key="idx" class="mt10" shadow="hover">
-          <template #header>
-            <div class="card-header">
-              <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveRightsItem(idx)">删除</el-button>
-            </div>
-          </template>
-          <el-form-item label="权益描述" prop="rightsDesc" class="w100">
-            <el-input
-                maxlength="500"
-                show-word-limit
-                type="textarea"
-                :rows="2"
-                v-model.trim="rights.rightsDesc"
-                placeholder="权益描述"
-                clearable
-                class="w100">
-            </el-input>
-          </el-form-item>
-
-          <el-row :gutter="20">
-            <el-col :span="12">
-              <el-form-item label="最小充值金额(元)" prop="amountMin">
-                <el-input-number
-                    controls-position="right"
-                    v-model.trim="rights.amountMin"
-                    placeholder="最小充值金额"
-                    clearable
-                    :min="0"
-                    class="wd200">
-                </el-input-number>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="最大充值金额(元)" prop="amountMin">
-                <el-input-number
-                    controls-position="right"
-                    v-model.trim="rights.amountMax"
-                    placeholder="最大充值金额"
-                    clearable
-                    :min="0"
-                    class="wd200">
-                </el-input-number>
-              </el-form-item>
+<!--        服务费折扣权益 -->
+        <template v-if="state.ruleForm.discountType==='RechargeRights'">
+          <el-card v-for="(rights,idx) in state.ruleForm.rechargeRightsList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveRightsItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="权益名称" prop="rights.name">
+                  <el-input
+                      v-model="rights.name"
+                      placeholder="名称"
+                      clearable
+                      class="wd200 ">
+                  </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+            <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+              <el-input
+                  maxlength="500"
+                  show-word-limit
+                  type="textarea"
+                  :rows="2"
+                  v-model.trim="rights.rightsDesc"
+                  placeholder="权益描述"
+                  clearable
+                  class="w100">
+              </el-input>
+            </el-form-item>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="最小充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMin" :ratio="100"  placeholder="最小充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMin"
+                      placeholder="最小充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="最大充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMax" :ratio="100"  placeholder="最大充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMax"
+                      placeholder="最大充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
 
-            </el-col>
-          </el-row>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣" prop="discount">
+                  <el-input-number
+                      :controls="false"
+                      v-model.trim="rights.discount"
+                      placeholder="折扣:100代表无折扣,75代表75折"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="rights.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
 
+          </el-card>
+        </template>
 
-          <el-row :gutter="20">
-            <el-col :span="12">
-              <el-form-item label="折扣" prop="amountMin">
-                <el-input-number
-                    :controls="false"
-                    v-model.trim="rights.discount"
-                    placeholder="折扣:100代表无折扣,75代表75折"
-                    clearable
-                    class="wd200">
-                </el-input-number>
-              </el-form-item>
-            </el-col>
-            <el-col :span="12">
-              <el-form-item label="有效期(天)" prop="validity">
-                <el-input-number
-                    :min="1"
-                    :controls="false"
-                    v-model.trim="rights.validity"
-                    placeholder="有效天数"
+<!--        优惠券-->
+        <template v-else>
+          <el-card v-for="(coupon,idx) in state.ruleForm.couponList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveCouponItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="名称" prop="coupon.name">
+                <el-input
+                      v-model="coupon.name"
+                      placeholder="名称"
+                      clearable
+                      class="wd200 ">
+                  </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+              <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+                <el-input
+                    maxlength="500"
+                    show-word-limit
+                    type="textarea"
+                    :rows="2"
+                    v-model.trim="coupon.couponDesc"
+                    placeholder="权益描述"
                     clearable
-                    class="wd200">
-                </el-input-number>
+                    class="w100">
+                </el-input>
               </el-form-item>
-            </el-col>
-          </el-row>
 
-        </el-card>
+
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣类型" prop="couponType">
+                  <ext-d-select
+                      v-model="coupon.couponType"
+                      placeholder="折扣类型"
+                      type="Activity.couponType"
+                      clearable
+                      class="wd200 ">
+                  </ext-d-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="coupon.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+
+            <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="最小服务费金额(元)" prop="minServiceMoney">
+                    <ext-input-number   class="wd200 "  v-model="coupon.minServiceMoney" :ratio="100"  placeholder="最小服务费金额"></ext-input-number>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="折扣" prop="discount">
+                    <el-input-number
+                        :controls="false"
+                        v-model.trim="coupon.discount"
+                        placeholder="折扣:100代表无折扣,75代表75折;折扣金额(分)"
+                        clearable
+                        class="wd200">
+                    </el-input-number>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+
+
+          </el-card>
+        </template>
+
 
       </el-form>
 
@@ -245,6 +353,7 @@ 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";
+import ExtInputNumber from "/@/components/form/ExtInputNumber.vue";
 
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['refresh']);
@@ -254,7 +363,8 @@ const initState = () => ({
   action: '',
   ruleForm: {
     id: 0,
-    rechargeRightsList: [] as Array<any>
+    rechargeRightsList: [] as Array<any>,
+    couponList: [] as Array<any>,
   },
   btnLoading: false,
   dialog: {
@@ -318,10 +428,10 @@ const onSubmit = () => {
       }
     }
 
-    state.ruleForm.rechargeRightsList.forEach((rights: any) => {
+/*    state.ruleForm.rechargeRightsList.forEach((rights: any) => {
       rights.amountMin = rights.amountMin * 100;
       rights.amountMax = rights.amountMax * 100;
-    })
+    })*/
   }
 
   formRef.value.validate((valid: boolean) => {
@@ -350,10 +460,10 @@ const handleFormChange = (formData: any) => {
 const loadData = (id: number) => {
   $get(`activity/${id}`).then((res: any) => {
     if (res && res.rechargeRightsList) {
-      res.rechargeRightsList.forEach((rights: any) => {
+/*      res.rechargeRightsList.forEach((rights: any) => {
         rights.amountMin = rights.amountMin / 100;
         rights.amountMax = rights.amountMax / 100;
-      })
+      })*/
     }
     state.ruleForm = res;
     state.ruleForm.rechargeRightsList = res.rechargeRightsList || []
@@ -369,18 +479,30 @@ const handleStationChange = (applyStation: number) => {
 }
 
 const handleAddRightsItem = () => {
-  state.ruleForm.rechargeRightsList.unshift({
-    rightsDesc: '',
-    amountMin: 0,
-    amountMax: 0,
-    discount: 100,
-    validity: 0
-  })
+  if(state.ruleForm.discountType==='RechargeRights'){
+    state.ruleForm.rechargeRightsList.unshift({
+      rightsDesc: '',
+      amountMin: 0,
+      amountMax: 0,
+      validity: 1
+    })
+  }else if(state.ruleForm.discountType==='Coupon'){
+    state.ruleForm.couponList.unshift({
+      couponDesc: '',
+      couponType: 'Discount',
+      minServiceMoney: 0,
+      validity: 1
+    })
+  }
+
 }
 
 const handleRemoveRightsItem = (idx: number) => {
   state.ruleForm.rechargeRightsList.splice(idx, 1)
 }
+const handleRemoveCouponItem = (idx: number) => {
+  state.ruleForm.couponList.splice(idx, 1)
+}
 
 // 暴露变量
 defineExpose({

+ 2 - 2
admin-web/src/views/admin/activity/index.vue

@@ -131,8 +131,8 @@
               <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.list'" size="small" plain type="success" @click="onRowClick('view',row)">查看</el-button>
-              <el-button v-auth="'banner.modify'" v-if="row.status==0||row.status==1"  size="small" plain type="danger" @click="onRowTerminal(row)">终止</el-button>
+              <el-button v-auth="'activity.list'" size="small" plain type="success" @click="onRowClick('view',row)">查看</el-button>
+              <el-button v-auth="'activity.modify'" v-if="row.status==0||row.status==1"  size="small" plain type="danger" @click="onRowTerminal(row)">终止</el-button>
             </template>
             <template v-else>
               <div>{{ row[field.prop] }}</div>

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

@@ -0,0 +1,490 @@
+<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">
+
+        <div class="sub-group-bottom"> 基本信息</div>
+        <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="remark" class="w100">
+          <el-input
+              maxlength="500"
+              show-word-limit
+              type="textarea"
+              :rows="3"
+              v-model.trim="state.ruleForm.activityDesc"
+              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>
+<!--        RechargeRights  "Coupon"-->
+        <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" v-if="state.ruleForm.discountType==='RechargeRights'">
+          <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>
+
+        <div class="sub-group-bottom"> 关联站点</div>
+<!--        <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"
+              data-key=""
+              clearable
+              class="w100 mt5">
+          </ext-select>
+        </el-form-item>
+
+        <div class="sub-group-bottom"> 关联权益</div>
+<!--        <el-divider content-position="left">关联权益</el-divider>-->
+        <el-button size="small" plain type="success" class="ml10" @click="handleAddRightsItem">
+          <SvgIcon name="ele-FolderAdd"/>
+          新增权益
+        </el-button>
+
+<!--        服务费折扣权益 -->
+        <template v-if="state.ruleForm.discountType==='RechargeRights'">
+          <el-card v-for="(rights,idx) in state.ruleForm.rechargeRightsList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveRightsItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+            <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+              <el-input
+                  maxlength="500"
+                  show-word-limit
+                  type="textarea"
+                  :rows="2"
+                  v-model.trim="rights.rightsDesc"
+                  placeholder="权益描述"
+                  clearable
+                  class="w100">
+              </el-input>
+            </el-form-item>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="最小充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMin" :ratio="100"  placeholder="最小充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMin"
+                      placeholder="最小充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="最大充值金额(元)" prop="amountMin">
+                  <ext-input-number  class="wd200"  v-model="rights.amountMax" :ratio="100"  placeholder="最大充值金额"></ext-input-number>
+<!--                  <el-input-number
+                      controls-position="right"
+                      v-model.trim="rights.amountMax"
+                      placeholder="最大充值金额"
+                      clearable
+                      :min="0"
+                      class="wd200">
+                  </el-input-number>-->
+                </el-form-item>
+
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣" prop="discount">
+                  <el-input-number
+                      :controls="false"
+                      v-model.trim="rights.discount"
+                      placeholder="折扣:100代表无折扣,75代表75折"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="rights.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+          </el-card>
+        </template>
+
+<!--        优惠券-->
+        <template v-else>
+          <el-card v-for="(coupon,idx) in state.ruleForm.couponList" :key="idx" class="mt10" shadow="hover">
+            <template #header>
+              <div class="card-header">
+                <el-button class="button" plain type="danger" size="small" v-auth="'activity.modify'" @click="handleRemoveCouponItem(idx)">删除</el-button>
+              </div>
+            </template>
+
+
+
+              <el-form-item label="权益描述" prop="rightsDesc" class="w100">
+                <el-input
+                    maxlength="500"
+                    show-word-limit
+                    type="textarea"
+                    :rows="2"
+                    v-model.trim="coupon.couponDesc"
+                    placeholder="权益描述"
+                    clearable
+                    class="w100">
+                </el-input>
+              </el-form-item>
+
+
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="折扣类型" prop="couponType">
+                  <ext-d-select
+                      @on-change="handleStationChange"
+                      v-model="coupon.couponType"
+                      placeholder="折扣类型"
+                      type="Activity.couponType"
+                      clearable
+                      class="wd200 ">
+                  </ext-d-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="有效期(天)" prop="validity">
+                  <el-input-number
+                      :min="1"
+                      :controls="false"
+                      v-model.trim="coupon.validity"
+                      placeholder="有效天数"
+                      clearable
+                      class="wd200">
+                  </el-input-number>
+                </el-form-item>
+              </el-col>
+            </el-row>
+
+
+            <el-row :gutter="20">
+                <el-col :span="12">
+                  <el-form-item label="最小服务费金额(元)" prop="minServiceMoney">
+                    <ext-input-number   class="wd200 "  v-model="coupon.minServiceMoney" :ratio="100"  placeholder="最小服务费金额"></ext-input-number>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="折扣" prop="discount">
+                    <el-input-number
+                        :controls="false"
+                        v-model.trim="coupon.discount"
+                        placeholder="折扣:100代表无折扣,75代表75折;折扣金额(分)"
+                        clearable
+                        class="wd200">
+                    </el-input-number>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
+
+
+          </el-card>
+        </template>
+
+
+      </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";
+import ExtInputNumber from "/@/components/form/ExtInputNumber.vue";
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+const formRef = ref();
+//定义初始变量,重置使用
+const initState = () => ({
+  action: '',
+  ruleForm: {
+    id: 0,
+    rechargeRightsList: [] as Array<any>,
+    couponList: [] as Array<any>,
+  },
+  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 = () => {
+  let rechargeRightsList = state.ruleForm.rechargeRightsList;
+  if (!u.isEmptyOrNull(rechargeRightsList)) {
+    for (let i = 0; i < rechargeRightsList.length; i++) {
+      const item: any = rechargeRightsList[i];
+      if (item.amountMax <=0) {
+        Msg.message(`权益最大充值金额不能小于0`, 'error')
+        return false;
+      }
+
+      if (item.amountMin > item.amountMax) {
+        Msg.message(`权益最小充值金额不能大于最大充值金额`, 'error')
+        return false;
+      }
+    }
+
+/*    state.ruleForm.rechargeRightsList.forEach((rights: any) => {
+      rights.amountMin = rights.amountMin * 100;
+      rights.amountMax = rights.amountMax * 100;
+    })*/
+  }
+
+  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) => {
+    if (res && res.rechargeRightsList) {
+/*      res.rechargeRightsList.forEach((rights: any) => {
+        rights.amountMin = rights.amountMin / 100;
+        rights.amountMax = rights.amountMax / 100;
+      })*/
+    }
+    state.ruleForm = res;
+    state.ruleForm.rechargeRightsList = res.rechargeRightsList || []
+  })
+}
+
+const handleStationChange = (applyStation: number) => {
+  console.log(applyStation)
+  if (applyStation == 0) {
+    // state.ruleForm.stationIds = [];
+  }
+
+}
+
+const handleAddRightsItem = () => {
+  if(state.ruleForm.discountType==='RechargeRights'){
+    state.ruleForm.rechargeRightsList.unshift({
+      rightsDesc: '',
+      amountMin: 0,
+      amountMax: 0,
+      validity: 1
+    })
+  }else if(state.ruleForm.discountType==='Coupon'){
+    state.ruleForm.couponList.unshift({
+      couponDesc: '',
+      couponType: 'Discount',
+      minServiceMoney: 0,
+      validity: 1
+    })
+  }
+
+}
+
+const handleRemoveRightsItem = (idx: number) => {
+  state.ruleForm.rechargeRightsList.splice(idx, 1)
+}
+const handleRemoveCouponItem = (idx: number) => {
+  state.ruleForm.couponList.splice(idx, 1)
+}
+
+// 暴露变量
+defineExpose({
+  open
+});
+
+
+</script>

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

@@ -0,0 +1,302 @@
+<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.activityName"
+            placeholder="活动名称"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.couponName"
+            placeholder="优惠券名称"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </el-input>
+        <el-input
+            v-model="state.formQuery.userName"
+            placeholder="用户名称"
+            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>
+        <ext-d-select
+            v-model="state.formQuery.couponType"
+            placeholder="券种"
+            type="Activity.couponType"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+
+        <ext-d-select
+            placeholder="用户状态"
+            v-model="state.formQuery.status"
+            type="User.status"
+            clearable
+            @blur="loadData(true)"
+            class="wd150 mr10">
+        </ext-d-select>
+
+
+        <ext-d-select
+            placeholder="使用状态"
+            v-model="state.formQuery.usageStatus"
+            type="UserCoupon.usage"
+            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-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==='couponType'">
+              <ext-d-label type="Activity.couponType" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='status'">
+              <ext-d-label type="User.status" v-model="row[field.prop]"></ext-d-label>
+            </template>
+            <template v-else-if="field.prop==='usageStatus'">
+              <ext-d-label type="UserCoupon.usageStatus" 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   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 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: 'activityName', resizable: true, width: 170, fixed: 'left'
+      },
+      {label: '活动时间', prop: 'period', resizable: true, width: 350},
+      {label: '优惠券名称', prop: 'couponName', align: 'center', width: 230},
+      {label: '用户名', prop: 'userName', resizable: true, width: 130},
+      {label: '手机号', prop: 'mobilePhone', resizable: true, width: 130},
+      {label: '有效期', prop: 'validity', resizable: true, width: 130},
+      {label: '用户状态', prop: 'status', resizable: true, width: 130},
+      {label: '使用状态', prop: 'usageStatus', resizable: true, width: 130},
+      {label: '优惠允许叠加', prop: 'allowStacke', 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(`/user-coupon/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 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>

+ 0 - 2
admin/src/main/java/com/kym/admin/jobs/ActivityDelayJob.java

@@ -14,8 +14,6 @@ import com.kym.service.admin.RechargeRightsService;
 import com.kym.service.jobs.DelayService;
 import com.kym.service.jobs.DelayedItem;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.context.annotation.Scope;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.event.EventListener;
 import org.springframework.stereotype.Service;

+ 16 - 0
database/5.sql

@@ -0,0 +1,16 @@
+-- charge_admin
+insert into charge_admin.t_permission (id, pid, name, value, weight, create_time, update_time)
+values
+    (73, 0, '优惠券', 'coupon.list', 1, '2024-06-10 12:16:08', '2024-06-10 12:16:08'),
+    (74, 46, '查看', 'coupon.list', 1, '2024-06-10 12:16:08', '2024-06-10 12:16:08'),
+    (75, 46, '编辑', 'coupon.modify', 2, '2024-06-10 12:16:08', '2024-06-10 12:16:08'),
+    (76, 46, '删除', 'coupon.delete', 3, '2024-06-10 12:16:08', '2024-06-10 12:16:08'),
+    (77, 46, '新增', 'coupon.add', 1, '2024-06-10 12:16:08', '2024-06-10 12:16:08');
+
+-- charge_app
+insert into t_data_dict ( code, name, weight, value, remark, create_time, update_time)
+values
+    ( 'Activity.couponType', '折扣券 ', 1, 'Discount', null, '2023-10-29 15:44:18', '2023-10-29 15:44:18'),
+    ( 'Activity.couponType', '满减券', 2, 'FullDiscount', null, '2023-10-29 15:44:18', '2023-10-29 15:44:18'),
+    ( 'UserCoupon.usage', '未使用 ', 1, '0', null, '2023-10-29 15:44:18', '2023-10-29 15:44:18'),
+    ( 'UserCoupon.usage', '已使用', 2, '1', null, '2023-10-29 15:44:18', '2023-10-29 15:44:18');

+ 5 - 0
entity/src/main/java/com/kym/entity/admin/vo/UserCouponsIssueVo.java

@@ -12,6 +12,11 @@ public class UserCouponsIssueVo {
      */
     private List<Long> userIds;
 
+    /**
+     * 手机号列表
+     */
+    private List<String> phones;
+
     /**
      * 优惠券id
      */

+ 12 - 0
service/src/main/java/com/kym/service/admin/impl/ActivityServiceImpl.java

@@ -37,6 +37,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import static com.kym.entity.admin.Activity.APPLY_STATION_部分站点;
+import static com.kym.entity.admin.Activity.DISCOUNT_TYPE_优惠券;
 import static com.kym.entity.admin.Activity.DISCOUNT_TYPE_服务费折扣权益;
 
 
@@ -192,8 +193,10 @@ public class ActivityServiceImpl extends MPJBaseServiceImpl<ActivityMapper, Acti
         var list = lambdaQuery()
                 .like(!CommUtil.isEmptyOrNull(params.getName()), Activity::getName, params.getName())
                 .eq(params.getStatus() != null, Activity::getStatus, params.getStatus())
+                .eq(null!=params.getDiscountType(), Activity::getDiscountType, params.getDiscountType())
                 .gt(params.getStartTime() != null, Activity::getStartTime, params.getStartTime())
                 .lt(params.getEndTime() != null, Activity::getEndTime, params.getEndTime())
+                .orderByDesc(Activity::getCreateTime)
                 .list();
         return new PageBean<>(list);
     }
@@ -218,6 +221,15 @@ public class ActivityServiceImpl extends MPJBaseServiceImpl<ActivityMapper, Acti
                 var stationList = stationService.lambdaQuery().in(Station::getStationId, activityStationIds).list();
                 activityVo.setStationList(stationList);
             }
+        }else if(activity.getDiscountType().equals(DISCOUNT_TYPE_优惠券)){
+            List<Coupon> couponList = couponService.lambdaQuery().eq(Coupon::getActivityId, activityId).list();
+            activityVo.setCouponList(couponList);
+            var activityStationIds = activityStationService.lambdaQuery().eq(ActivityStation::getActivityId, activityId).list().stream().map(ActivityStation::getStationId).toList();
+            if (CommUtil.isNotEmptyAndNull(activityStationIds)) {
+                activityVo.setStationIds(activityStationIds);
+                var stationList = stationService.lambdaQuery().in(Station::getStationId, activityStationIds).list();
+                activityVo.setStationList(stationList);
+            }
         }
         return activityVo;
     }

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

@@ -21,6 +21,9 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
 
 /**
  * <p>
@@ -58,6 +61,7 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
         PageHelper.startPage(params.getPageNum(), params.getPageSize());
         var res = lambdaQuery()
                 .like(CommUtil.isNotEmptyAndNull(params.getName()), Coupon::getName, params.getName())
+                .eq(CommUtil.isNotEmptyAndNull(params.getCouponType()), Coupon::getCouponType, params.getCouponType())
                 .orderByAsc(Coupon::getEndTime)
                 .list();
         return new PageBean<>(res);
@@ -65,7 +69,12 @@ public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> impleme
 
     @Override
     public void issueCoupons(UserCouponsIssueVo userCouponsIssue) {
-        var users = userService.lambdaQuery().in(User::getId, userCouponsIssue.getUserIds()).list();
+        List<User> users = new ArrayList<>();
+        if (CommUtil.isNotEmptyAndNull(userCouponsIssue.getUserIds())) {
+            users = userService.lambdaQuery().in(User::getId, userCouponsIssue.getUserIds()).list();
+        } else if (CommUtil.isNotEmptyAndNull(userCouponsIssue.getPhones())) {
+            users = userService.lambdaQuery().in(User::getMobilePhone, userCouponsIssue.getPhones()).list();
+        }
         var coupons = lambdaQuery().in(Coupon::getId, userCouponsIssue.getCouponIds()).list();
         users.forEach(user -> {
             for (Coupon coupon : coupons) {

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

@@ -255,7 +255,9 @@ public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implem
     public PageBean<CustomUserVo> listCustomUser(CommonQueryParam params) {
         List<Long> userIds = null;
         if (!CommUtil.isEmptyOrNull(params.getMobilePhone())) {
-            userIds = lambdaQuery().eq(User::getMobilePhone, params.getMobilePhone()).list().stream().map(User::getId).toList();
+            userIds = lambdaQuery().
+                    eq(User::getMobilePhone, params.getMobilePhone())
+                    .eq(!CommUtil.isEmptyOrNull(params.getStatus()),User::getStatus, params.getStatus()).list().stream().map(User::getId).toList();
         }
         PageHelper.startPage(params.getPageNum(), params.getPageSize());
         var result = baseMapper.listUser(userIds);