jiangping
2025-02-19 c977f0e2fd86ca3e9e3ad3961f702206c41d2767
Merge remote-tracking branch 'origin/2.0.1' into 2.0.1

# Conflicts:
# server/services/src/main/java/com/doumee/dao/business/model/DiscountMember.java
已添加19个文件
已删除1个文件
已修改26个文件
38470 ■■■■■ 文件已修改
admin/package-lock.json 24847 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/package.json 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/public/index.html 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/assets/style/style.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/assets/style/variables.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/base/BasePageTemp.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/QueryForm/index.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/QueryForm/queryForm.vue 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/common/RichEditor.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/layouts/Breadcrumb.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/components/ComboDetail.vue 402 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/components/Edit.vue 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/components/OrderDetail.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/components/config.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/index.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/order.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/combo/record.vue 494 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/operation/components/Map.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/operation/components/SitesWindow.vue 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/operation/components/TramEdit.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/operation/site.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/operation/tram.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/yarn.lock 10478 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/platform/src/main/java/com/doumee/api/business/DiscountMemberController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/platform/src/main/java/com/doumee/api/business/GoodsorderController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/constants/Constants.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/utils/DateUtil.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/DiscountLogMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Discount.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DiscountLog.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DiscountMember.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Goodsorder.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Transactions.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/vo/DiscountGoodsorderExportVO.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/web/request/DiscountMemberDTO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/web/response/GoodsorderDetailVO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DiscountMemberService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DiscountService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/GoodsorderService.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DiscountMemberServiceImpl.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DiscountServiceImpl.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/TransactionsServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/BusinessApi.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/HomeApi.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/package-lock.json
ÎļþÌ«´ó
admin/package.json
@@ -4,6 +4,7 @@
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "dev": "vue-cli-service serve",
    "build:pro": "vue-cli-service build",
    "build:dev": "vue-cli-service build --mode staging",
    "lint": "vue-cli-service lint",
@@ -50,7 +51,7 @@
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "lint-staged": "^9.5.0",
      "node-sass": "^4.14.1",
    "node-sass": "^4.14.1",
    "vue-cli-plugin-element-ui": "~1.1.4",
    "vue-template-compiler": "^2.6.14"
  },
admin/public/index.html
@@ -18,6 +18,7 @@
  <div id="app"></div>
  <!-- built files will be auto injected -->
</body>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=HLMBZ-BQ6LM-AY46S-6B5SN-C3DJT-MVBKL"></script>
<script>
</script>
admin/src/assets/style/style.scss
@@ -51,7 +51,52 @@
.el-transfer__buttons {
  padding: 0 16px !important;
}
.main_app{
  padding: 6px 16px;
}
.flex1{
  flex: 1;
}
.primaryColor{
  color: $primary-color;
}
.pointer{
  cursor: pointer;
}
.w200{
  width: 200px !important;
}
.w400{
  width: 400px !important;
}
.w100{
  width: 100px !important;
}
.ml16{
  margin-left: 16px;
}
.ml10{
  margin-left: 10px;
}
.mr16{
  margin-right: 16px;
}
.mr10{
  margin-right: 10px;
}
.placeholder9{
  color: #999999;
  font-size: 12px;
}
.df_ac{
  display: flex;
  align-items: center;
}
.table_btns{
  background-color: #fff;
  padding: 16px 16px 10px 16px;
}
.table-header {
  background-color: #F5F6F8!important;
  th {
admin/src/assets/style/variables.scss
@@ -1,5 +1,6 @@
// ä¸»è‰²è°ƒ
$primary-color: #216EEE;
$primaryColor: #216EEE;
$primary-title-start-color: #3582ff;
$primary-title-color: #216EcE;
$primary-color-sel: #0046c6;
admin/src/components/base/BasePageTemp.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
<script>
import Pagination from '@/components/common/Pagination'
import QueryForm from '@/components/common/QueryForm'
export default {
  name: 'BasePageTemp',
  components: {
    Pagination,
    QueryForm,
  },
  data () {
    return {
      loading: false,
      pagination: {
        pageSize: 10,
        page: 1,
        total: 0
      },
      filters: {},
      list: [],
      total: 0,
    }
  },
  methods: {
    getList(){},
    clear() {
      this.filters = {}
      this.pagination.pageSize = 10
      this.pagination.page = 1
      this.getList()
    },
    handleSizeChange(capacity) {
      this.pagination.pageSize = capacity
      this.getList()
    }
  }
}
</script>
admin/src/components/common/QueryForm/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
import QueryForm from './queryForm.vue'
export default QueryForm
admin/src/components/common/QueryForm/queryForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,233 @@
<template>
  <div class="doumee-filter">
    <el-form inline label-suffix=":" @submit.native.prevent>
      <template v-for="(item, index) in queryFormConfig.formItems">
        <el-form-item v-if="item.type === 'input' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-input v-model="searchForm[item.filed]"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :placeholder="item.label" class="w200"
            @change="changeForm(item.filed)" @keyup.enter.native="handlekeyup(item.keyup || false)" />
        </el-form-item>
        <el-form-item v-if="item.type === 'moneySelect' && (showZk || index < listLength)" :label="item.label" :key="item.label">
          <el-input v-model="searchForm[item.filedStrt]"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :placeholder="item.start" style="width: 150px" />
          <div class="date-division-line" style="margin-left: 10px;margin-bottom: 0;">~</div>
          <el-input v-model="searchForm[item.filedEnd]"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :placeholder="item.end" style="width: 150px" />
        </el-form-item>
        <el-form-item v-if="item.type === 'select' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-select v-model="searchForm[item.filed]" :filterable="item.filterable || true"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :placeholder="item.label" class="w200"
            @change="changeForm(item.filed)">
            <el-option v-for="opt, i in item.options" :key="i" :value="item.valueCode ? opt[item.valueCode] : opt.value"
              :label="item.labelCode ? opt[item.labelCode] : opt.label" />
          </el-select>
        </el-form-item>
        <el-form-item v-if="item.type === 'date' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-date-picker v-model="searchForm[item.filed]" type="date" value-format="yyyy-MM-dd" class="w200"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :placeholder="item.label" :picker-options="item.pickerOptions || {}"
            @change="changeForm(item.filed)" />
        </el-form-item>
        <el-form-item v-if="item.type === 'daterange' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-date-picker v-model="searchForm[item.filed]" value-format="yyyy-MM-dd" type="daterange"
            range-separator="至"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :start-placeholder="item.start || ''" :end-placeholder="item.end || ''"
            :picker-options="item.pickerOptions || {}" class="w400" @change="changeForm(item.filed)" />
        </el-form-item>
        <el-form-item v-if="item.type === 'timePicker' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-time-picker v-model="searchForm[item.filed]" is-range range-separator="至" format="HH:mm"
            value-format="HH:mm" start-placeholder="开始时间" end-placeholder="结束时间" placeholder="选择时间范围" class="w400"
            @change="changeForm(item.filed)" />
        </el-form-item>
        <el-form-item v-if="item.type === 'datetimerange' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <el-date-picker v-model="searchForm[item.filed]" format="yyyy-MM-dd HH:mm:ss"
            value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" :default-time="['00:00:00', '23:59:59']"
            :picker-options="item.pickerOptions || pickerOptions" range-separator="至"
            :clearable="(item.clearable !== null && item.clearable !== undefined && item.clearable !== '') ? item.clearable : true"
            :start-placeholder="item.start || '开始时间'" :end-placeholder="item.end || '结束时间'" class="w400"
            @change="changeForm(item.filed)" />
        </el-form-item>
        <el-form-item v-if="item.type === 'slot' && (showZk || index < listLength)" :label="item.label" :key="item.filed">
          <slot :name="item.filed" />
        </el-form-item>
      </template>
      <!-- æ“ä½œ -->
      <el-form-item>
        <el-button style="width: 56px" type="primary" @click="handleQuery">搜索</el-button>
        <el-button style="width: 56px" v-if="showQk" @click="clear">重置</el-button>
        <slot name="btns" />
        <template v-if="queryFormConfig.formItems.length > listLength">
          <el-button v-if="!showZk" type="text" @click="zkBtn">展开<i
              class="el-icon-caret-bottom primaryColor" /></el-button>
          <el-button v-if="showZk" type="text" @click="zkBtn">收起<i class="el-icon-caret-top primaryColor" /></el-button>
        </template>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: Object,
      default: () => { }
    },
    showQk: {
      type: Boolean,
      default: true
    },
    listLength: {
      type: Number,
      default: 6
    },
    queryFormConfig: {
      type: Object,
      default: () => { }
    }
  },
  data() {
    return {
      showZk: false,
      pickerOptions: {
        shortcuts: [{
          text: '近7天',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(end.getTime() - 3600 * 1000 * 24 * 6)
            picker.$emit('pick', [start, end])
          }
        },
        {
          text: '近30天',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(end.getTime() - 3600 * 1000 * 24 * 29)
            picker.$emit('pick', [start, end])
          }
        },
        {
          text: '近60天',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(end.getTime() - 3600 * 1000 * 24 * 59)
            picker.$emit('pick', [start, end])
          }
        },
        {
          text: '近90天',
          onClick(picker) {
            const end = new Date()
            const start = new Date()
            start.setTime(end.getTime() - 3600 * 1000 * 24 * 89)
            picker.$emit('pick', [start, end])
          }
        }],
        disabledDate(time) {
          var curDate = new Date(new Date().toLocaleDateString()).getTime()
          var preDate = new Date(curDate + 24 * 60 * 60 * 1000 - 1)
          return time.getTime() > preDate
        }
      }
    }
  },
  emits: ['input', 'handleQuery', 'clear'],
  computed: {
    searchForm: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    }
  },
  methods: {
    handleQuery() {
      this.$emit('handleQuery')
    },
    handlekeyup(pd) {
      if (pd) {
        this.$emit('handleQuery')
      }
    },
    changeForm(filed) {
      this.$emit('changeForm', filed)
    },
    zkBtn() {
      this.showZk = !this.showZk
      this.$emit('zkBtn', this.zk)
    },
    clear() {
      this.$emit('clear')
    }
  }
}
</script>
<style lang="scss" scoped>
.doumee-filter {
  display: flex;
  align-items: center;
  position: relative;
  flex-wrap: wrap;
  align-items: center;
  background-color: #fff;
  border-bottom: 10px solid #f7f8f9;
  /* margin: 0 -20px; */
  padding: 16px 16px 0 16px;
  .el-input,
  .el-select {
    width: 200px;
    margin-right: 16px;
    margin-bottom: 16px;
    height: 36px;
    .el-input__inner {
      /* color: $textColor; */
      padding: 0 16px;
    }
  }
  .el-date-editor {
    width: 200px;
    margin-right: 16px;
    margin-bottom: 16px;
    .el-input__inner {
      padding-left: 30px;
    }
  }
  .el-button {
    margin-bottom: 16px;
  }
  .el-form-item {
    margin-bottom: 16px;
    margin-right: 16px;
    .el-input,
    .el-select,
    .el-date-editor {
      margin-right: 0;
    }
    .el-input,
    .el-select,
    .el-date-editor,
    .el-button {
      margin-bottom: 0;
    }
  }
}
</style>
admin/src/components/common/RichEditor.vue
@@ -143,7 +143,7 @@
      this.editor = Object.seal(editor)
    },
    onChange (editor) {
      console.log(this.html);
      // console.log(this.html);
      // debugger
      if (!this.html||this.content.content==this.html) {
        return
admin/src/layouts/Breadcrumb.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
<template>
  <div class="table-header">
    <el-breadcrumb separator="/">
      <el-breadcrumb-item v-for="path in paths" :key="path">{{ path }}</el-breadcrumb-item>
    </el-breadcrumb>
  </div>
</template>
<script>
export default {
  computed: {
    paths() {
      return this.$route.meta.paths
    }
  }
}
</script>
<style lang="scss" scoped>
.table-header {
  overflow: hidden;
  padding: 6px 16px 12px;
  flex-shrink: 0;
  // é¡µé¢è·¯å¾„
  .el-breadcrumb {
    .el-breadcrumb__item {
      .el-breadcrumb__inner {
        color: #ABB2BE;
        font-size: 12px;
      }
      &:last-of-type .el-breadcrumb__inner {
        color: #606263;
        font-size: 14px;
      }
    }
  }
}
</style>
admin/src/views/combo/components/ComboDetail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,402 @@
<template>
  <GlobalWindow title="套餐详情" :visible.sync="isShowModal" :confirm-working="isLoading" width="1000px" @close="close"
    @confirm="close">
    <div class="detail_header">
      <div class="sp_bew">
        <div class="left">
          <el-image v-if="detail.fullImgurl" :src="detail.fullImgurl" class="img" />
          <div class="title_wrap">
            <div>
              <span class="title">{{ detail.name }}</span>
              <span class="status">{{ detail.status === '1' ? '已启用' : '禁用' }}</span>
            </div>
            <div class="placeholder9 mt10">每日销售限量:{{ detail.circulationDay }}</div>
          </div>
        </div>
        <div class="right">
          <div class="item">
            <div class="key">总发放数量</div>
            <div class="value">{{ detail.circulationAll || '-' }}</div>
          </div>
          <div class="item">
            <div class="key">已销售数量</div>
            <div class="value">{{ detail.sellNum }}</div>
          </div>
          <div class="item">
            <div class="key">剩余数量</div>
            <div v-if="detail.circulationAll" class="value">{{ detail.surplusDay }}</div>
            <div v-else class="value">{{ detail.surplusDay || '-' }}</div>
          </div>
        </div>
      </div>
      <div class="createTime_wrap">
        <span class="item">创建时间:{{ detail.createTime }}</span>
        <span class="item">创建人:{{ detail.creator }}</span>
        <span class="item">最后更新时间:{{ detail.editTime }}</span>
        <span class="item">更新人:{{ detail.editor }}</span>
      </div>
    </div>
    <div class="separate" />
    <div class="property-title">套餐属性</div>
    <div class="line">
      <div class="key">套餐名称:</div>
      <div class="value">{{ detail.name }}</div>
    </div>
    <div class="line">
      <div class="key">套餐类型:</div>
      <div v-if="detail.mealsType == '0'" class="value">期限卡</div>
    </div>
    <div class="line">
      <div class="key">{{ detail.mealsType == '1' || detail.mealsType == '4' ? '每日骑行限制:' : '套餐次数:' }}</div>
      <div v-if="detail.mealsType == '1' || detail.mealsType == '4'" class="value">
        <span>{{ detail.inOutNum || '不限制' }}</span>
      </div>
      <div v-else class="value"><span>{{ detail.nums }}</span></div>
    </div>
    <div class="line">
      <div class="key">使用说明:</div>
      <div class="value">
        <div>{{ detail.content }}</div>
      </div>
    </div>
    <div class="property-title">适用规则</div>
    <div class="line">
      <div class="key">销售时间段:</div>
      <div class="value">{{ detail.circulationAll || '不限量' }}</div>
    </div>
    <div class="line">
      <div class="key">使用时间:</div>
      <div class="value">{{ detail.circulationDay }}</div>
    </div>
    <div class="line">
      <div class="key">适用范围:</div>
      <div class="value">{{ detail.useVenue }}</div>
    </div>
    <div class="line">
      <div class="key">适用项目:</div>
      <div class="value">{{ detail.useProject }}</div>
    </div>
    <div class="line">
      <div class="key">总限额:</div>
      <div class="value">{{ detail.useClassInfo || '不限制' }}</div>
    </div>
    <div class="line">
      <div class="key">每日限额:</div>
      <div class="value">{{ detail.useTeacherInfo || '不限额' }}</div>
    </div>
  </GlobalWindow>
</template>
<script>
// import { comboDetailPost, mealsCancelUse } from '@/api'
// import dayjs from 'dayjs'
import GlobalWindow from '@/components/common/GlobalWindow'
export default {
  name: 'ComboDetail',
  components: {
    GlobalWindow
  },
  data() {
    return {
      isShowModal: false,
      isLoading: false,
      activeTab: '0',
      detail: {},
      pagination: {
        page: 1,
        rows: 10
      },
      totalCount: 0,
      dataList: [],
      tableLoading: false
    }
  },
  created() {
    // this.detail = this.$route.query
    // this.comboDetail()
  },
  methods: {
    mealsCancelUseBtn(businessId, mealsMemberId) {
      const that = this
      this.$alert('确定要撤销使用?', '撤销使用', {
        confirmButtonText: '确定',
        callback: action => {
          if (action === 'confirm') {
            mealsCancelUse({
              param: {
                businessId, mealsMemberId
              }
            }).then((res) => {
              if (res.errorCode === '000000') {
                that.$message.success('撤销使用成功')
                that.comboDetail()
              }
            })
          }
        }
      })
    },
    getDetail(detail) {
      const { activeTab, pagination } = this
      const param = {
        listType: activeTab,
        type: detail.type,
        id: detail.id
      }
      this.tableLoading = true
      comboDetailPost({ pagination, param }).then(res => {
        this.tableLoading = false
        this.dataList = res.record.data || []
        this.totalCount = res.record.count || 0
        this.detail = { ...detail, useProject: res.record.useProject }
        this.$nextTick(() => {
          this.$refs.tableo.doLayout()
        })
      }, () => {
        this.tableLoading = false
      })
    },
    tabClick(val) {
      this.comboDetail(this.detail)
    },
    goBack() {
      this.$router.replace(`/combo/salesRecord`)
    },
    close() {
      this.isShowModal = false
      this.$emit('close')
    },
    currentPageChange(val) {
      this.pagination.page = val
      this.comboDetail()
    },
    pageSizeChange(val) {
      this.pagination.rows = val
      this.comboDetail()
    }
  }
}
</script>
<style lang="scss" scoped>
@import "@/assets/style/variables.scss";
.line {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
  font-size: 14px;
  // padding-left: 16px;
  .key {
    color: #666666;
    width: 100px;
    text-align: right;
  }
  .value {
    color: #222222;
  }
}
.detail_header {
  font-size: 14px;
  .sp_bew {
    display: flex;
    justify-content: space-between;
    .left {
      display: flex;
      .img {
        width: 50px;
        height: 50px;
        margin-right: 20px;
      }
      .title {
        font-size: 18px;
        font-weight: 600;
        color: #222222;
      }
      .status {
        border-radius: 2px;
        border: 1px solid $primaryColor;
        color: $primaryColor;
        margin-left: 10px;
      }
    }
    .right {
      display: flex;
      background-color: #F4F7FC;
      border-radius: 2px;
      .item {
        padding: 14px 20px;
        .key {
          font-size: 12px;
          color: #666666;
        }
        .value {
          margin-top: 8px;
          color: #222222;
          font-weight: 600;
        }
      }
    }
  }
  .createTime_wrap {
    display: flex;
    /* justify-content: flex-end; */
    margin: 16px 0;
    .item {
      margin-right: 30px;
      color: #999999;
    }
  }
}
.separate {
  height: 1px;
  margin: 20px -20px;
  padding: 0 20px;
  background-color: #DFE2E8;
}
.tip {
  font-size: 14px;
  line-height: 1.5;
  color: #333;
}
.property-title{
  margin-bottom: 16px;
  color: $primaryColor;
}
.device-uploader {
  width: 80px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #fff;
  cursor: pointer;
  border: 1px dashed #ccc;
  border-radius: 8px;
  transition: all 0.3s;
  margin-right: 20px;
  &:hover {
    border-color: $primaryColor;
    .uploader-icon {
      color: $primaryColor;
    }
  }
  ::v-deep .el-upload {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    .file {
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      background-size: contain;
      background-position: center;
      background-repeat: no-repeat;
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      .delete {
        background-color: rgba(0, 0, 0, 0.5);
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        display: none;
        align-items: center;
        justify-content: center;
        .icon {
          color: #fff;
        }
      }
      &:hover {
        .delete {
          display: flex;
        }
      }
    }
  }
  .uploader-icon {
    color: #aaaaaa;
    font-size: 22px;
    transition: all 0.3s;
  }
}
.count-style {
  display: inline-block;
  width: 45%;
  height: 60px;
}
.division-line {
  text-align: center;
  display: inline-block;
  color: #999;
  width: 50px;
}
.clock {
  display: inline-block;
}
.upload-style {
  vertical-align: middle;
  display: inline-block;
  // width: 60px;
  // height: 60px;
  border: none;
}
.img-style {
  margin: auto;
  background-color: #eee;
  width: 90px;
  height: 60px;
  vertical-align: middle;
}
.avatar {
  width: 60px;
  height: 60px;
  display: block;
}
.line-style {
  background-color: #dfe2e8;
  height: 1px;
  // width: 80%;
  // margin: 0 auto;
  margin-bottom: 20px;
}
</style>
admin/src/views/combo/components/Edit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
<template>
  <GlobalWindow :title="param.id ? '编辑套餐' : '新建套餐'" :visible.sync="isShowModal" :confirm-working="isLoading"
    width="1000px" @close="close" @confirm="confirm">
    <el-form :model="param" label-width="100px" ref="form" :rules="rules">
      <div class="form_title">基本信息</div>
      <el-form-item label="套餐名称" prop="name">
        <el-input class="w200" v-model="param.name" placeholder="请输入套餐名称" v-trim />
      </el-form-item>
      <el-form-item label="类型">
        <el-select class="w200" :disabled="true" v-model="param.warehouseId">
          <el-option label="期限卡" :value="0"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="每日骑行限制">
        <div>
          <el-radio v-model="param.aa" label="1">不限制</el-radio>
          <el-radio v-model="param.aa" label="2">限制时长</el-radio>
        </div>
      </el-form-item>
      <el-form-item label="销售价">
        <el-input class="w200" v-model="param.name" placeholder="请输入价格" v-trim />
      </el-form-item>
      <el-form-item label="划线价">
        <el-input class="w200" v-model="param.name" placeholder="请输入展示的划线价" v-trim />
      </el-form-item>
      <el-form-item label="销售渠道">
        <el-checkbox v-model="param.ada">小程序端</el-checkbox>
      </el-form-item>
      <el-form-item label="套餐图片">
        <div class="df_ac">
          <UploadAvatarImage :file="{ 'imgurlfull': param.fileFullUrl, 'imgurl': param.fileUrl }"
            :uploadData="{ folder: 'ywPatrol/' }" @uploadSuccess="uploadAvatarSuccess" @uploadEnd="isUploading = false"
            @uploadBegin="isUploading = true" />
          <div class="img_place">
            <div>上传图片不允许涉及政治敏感与色情;</div>
            <div>图片格式必须为:png,PNG,jpeg,jpg,JPG;不可大于2M;建议使用</div>
            <div>ng格式图片,以保持最佳效果;建议图片尺寸为144px*144px</div>
          </div>
        </div>
      </el-form-item>
      <el-form-item label="套餐简介">
        <el-input class="w400" v-model="param.name" placeholder="请输入套餐简介" v-trim />
      </el-form-item>
      <el-form-item label="套餐描述">
        <RichEditor :content="{ content: param.content }" @edit="param.content = $event"></RichEditor>
      </el-form-item>
      <div class="form_title">
        <div>适用规则</div>
        <div class="placeholder9">销售时间、使用时间等设置</div>
      </div>
      <el-form-item label="销售时段">
        <el-date-picker class="w400" v-model="param.value1" value-format="yyyy-MM-dd" type="daterange" range-separator=" ~ " />
      </el-form-item>
      <div class="df_ac">
        <el-form-item label="使用时间" class="mr16">
          <el-select v-model="param.aaadd" style="width: 140px;">
            <el-option label="固定日期" :value="0"></el-option>
            <el-option label="购买后生效" :value="1"></el-option>
            <el-option label="指定日期生效" :value="1"></el-option>
          </el-select>
        </el-form-item>
        <!-- <el-form-item label-width="80px" label="固定日期">
          <el-date-picker v-model="param.value1" value-format="yyyy-MM-dd" type="daterange" range-separator=" ~ " />
        </el-form-item> -->
        <!-- <el-form-item label-width="60px" label="有效期">
          <el-input style="width: 130px;" v-model="param.name" placeholder="请输入有效天数" v-trim />
        </el-form-item> -->
        <el-form-item label-width="0px">
          <div class="df_ac">
            <el-date-picker class="w200" v-model="param.dasdas" value-format="yyyy-MM-dd" type="date" />
            <span class="ml10 mr10">开始生效,有效期</span>
            <el-input style="width: 130px;" v-model="param.name" placeholder="请输入有效天数" v-trim />
            <span class="ml10">天</span>
          </div>
        </el-form-item>
      </div>
      <el-form-item label="适用范围">
        <el-checkbox-group v-model="param.checkList">
          <el-checkbox label="工作日"></el-checkbox>
          <el-checkbox label="节假日"></el-checkbox>
        </el-checkbox-group>
      </el-form-item>
      <el-form-item label="适用项目">
        <el-checkbox-group v-model="param.checkList">
          <el-checkbox label="自行车"></el-checkbox>
          <el-checkbox label="电动车"></el-checkbox>
        </el-checkbox-group>
      </el-form-item>
      <el-form-item label="总限额">
        <el-input class="w400" v-model="param.name" placeholder="请输入总发售数量" v-trim />
        <div class="placeholder9">请输入总发售数量,销量大于该数量后,不再支持销售;为空表示不限制</div>
      </el-form-item>
      <el-form-item label="日限额">
        <el-input class="w400" v-model="param.name" placeholder="请输入单日发售数量" v-trim />
        <div class="placeholder9">请输入日发售数量,当日销量大于该数量后,不再支持销售;为空表示不限制</div>
      </el-form-item>
    </el-form>
    <!--  -->
  </GlobalWindow>
</template>
<script>
import GlobalWindow from '@/components/common/GlobalWindow'
// import { fetchList as getStoreList } from '@/api/ywWarehouse'
// import { ywOutinboundCreate } from '@/api/store/index'
import UploadAvatarImage from '@/components/common/UploadAvatarImage'
import { StoreTypeOps, rules } from './config'
import { Message } from 'element-ui'
import RichEditor from '@/components/common/RichEditor'
// import dayjs from 'dayjs'
export default {
  name: 'OperaCategoryWindow',
  components: { GlobalWindow, UploadAvatarImage, RichEditor },
  data() {
    return {
      // è¡¨å•数据
      param: { checkList: [], content: '' },
      isShowModal: false,
      isShowSel: false,
      isLoading: false,
      isUploading: false,
      // éªŒè¯è§„则
      rules,
      storeList: [],
      list: [],
      StoreTypeOps: [],
      dataList: [],
    }
  },
  created() {
    // this.initData()
  },
  methods: {
    confirm() {
      this.$refs['form'].validate((valid) => {
        if (valid) {
          const { param, list } = this
          if (list.length == 0) return Message.warning('请先选择物料信息')
          let count = 0 // å…¥åº“数量必填
          list.forEach(item => {
            if (!item.stock) count++
          })
          if (count > 0) return Message.warning('请输入正确的入库数量')
          this.isLoading = true
          ywOutinboundCreate({
            ...param,
            recordList: list
          }).then(res => {
            this.isLoading = false
            Message.success('提交成功')
            this.$emit('success')
            this.close()
          }, () => {
            this.isLoading = false
          })
        }
      })
    },
    initData() {
      this.$set(this.param, 'doneDate', dayjs().format('YYYY-MM-DD'))
      getStoreList({ capacity: 9999, page: 1, model: { status: 0 } }).then(res => {
        this.storeList = res.records || []
      })
    },
    changeSel(val) {
      const list = val
      list.forEach(item => {
        const index = this.list.findIndex(i => i.id == item.id)
        if (index == -1) {
          item.materialId = item.id
          item.multifile = null
          item.createDate = null
          this.list.push(item)
        }
      })
    },
    uploadAvatarSuccess(file) {
      this.$set(this.param, 'fileUrl', file.imgurl)
      this.$set(this.param, 'fileFullUrl', file.imgurlfull)
    },
    handleOpenMaterial() {
      this.isShowSel = true
      this.$nextTick(() => {
        this.$refs.AssetSelRef.isShowModal = true
        this.$refs.AssetSelRef.getList()
      })
    },
    close() {
      this.isShowModal = false
      this.$emit('close')
    },
    getDetail(id) {
      getInfoById(id).then(res => {
        this.param = res
      })
    },
  }
}
</script>
<style lang="scss" scoped>
@import "@/assets/style/variables.scss";
.form_title {
  display: flex;
  align-items: center;
  color: $primary-color;
  margin-bottom: 16px;
  .placeholder9 {
    color: #999999;
    font-size: 12px;
    margin-left: 12px;
  }
}
.img_place{
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  margin-left: 12px;
  color: #999999;
  font-size: 12px;
  padding: 8px 0;
}
</style>
admin/src/views/combo/components/OrderDetail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
<template>
  <GlobalWindow :title="title" :visible.sync="visible" :confirm-working="isWorking">
    <div class="title">支付明细</div>
    <el-table :data="list" stripe border>
      <el-table-column prop="id" label="订单编号" show-overflow-tooltip align="center"></el-table-column>
      <el-table-column prop="onlineorderId" label="交易单号" show-overflow-tooltip align="center"></el-table-column>
      <el-table-column prop="refundType" label="交易类型" width="100px" align="center">
        <template slot-scope="{row}">
          {{ typeToStr(row.refundType) }}
        </template>
      </el-table-column>
      <el-table-column prop="money" label="交易金额(元)" width="100px" align="center"></el-table-column>
      <el-table-column prop="payWay" label="渠道" width="100px" align="center">
        <template slot-scope="{row}">
          {{ row.payWay==0? '微信' : '支付宝' }}
        </template>
      </el-table-column>
      <el-table-column prop="payDate" label="交易时间" width="150px" align="center"></el-table-column>
    </el-table>
    <div class="title">骑行记录</div>
    <el-table
      :data="memberRidesList"
      stripe
      border
    >
      <el-table-column prop="openid" label="用户" width="250px" show-overflow-tooltip align="center"></el-table-column>
      <el-table-column prop="bikeCode" label="车辆编号" width="100px" align="center"></el-table-column>
      <el-table-column prop="bikeType" label="车类型" width="200px" align="center"></el-table-column>
      <el-table-column prop="rideTime" label="借出时长(分)" width="200px" align="center"></el-table-column>
      <el-table-column prop="duration" label="计费时长(分)" width="200px" align="center"></el-table-column>
      <el-table-column prop="bikeType" label="车类型" width="150px" align="center"></el-table-column>
      <el-table-column prop="rentDate" label="借出时间" width="150px" align="center"></el-table-column>
      <el-table-column prop="backDate" label="还车时间" width="150px" align="center"></el-table-column>
      <el-table-column prop="closeStatus" fixed="right" label="结算状态" width="100px" align="center">
        <template slot-scope="{row}">
          {{ row.closeStatus == 0 ? '未结算' : '已结算' }}
        </template>
      </el-table-column>
    </el-table>
    <div slot="footer">
      <el-button @click="visible=false">返回</el-button>
    </div>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
// import GoodsOrderList from './goodsOrderList.vue'
export default {
  name: 'OperaSitesWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  data () {
    return {
      // è¡¨å•数据
      form: {
        goodsorderId: ''
      },
      list: [],
      memberRidesList: [],
      // 0结算退款 1强制结算退款 2结算后退款 [99: è™šæ‹Ÿtype æ”¯ä»˜æŠ¼é‡‘]
      type: [
        { label: '结算退款', id: 0 },
        { label: '平台自动结算退款', id: 1 },
        { label: '强制结算退款', id: 2 },
        { label: '结算后退款', id: 3 },
        { label: '支付押金', id: 99 },
      ],
    }
  },
  created() {
  },
  methods: {
    open(title, target) {
      debugger
      this.title = title
      this.visible = true
      // æ–°å»º
      this.form = target
      this.$nextTick(() => {
        this.list = target.payOrderDTOList
        this.memberRidesList = target.memberRidesList
        // this.$refs.goodsOrderList.reload(target.model)
      })
    },
    typeToStr(type) {
      let temp = this.type.find(item => item.id == type )
      return temp ? temp.label : '-'
    }
  },
}
</script>
<style scoped>
.title {
  font-size: 18px;
  font-weight: 600;
  color: #333;
  margin-bottom: 20px;
  margin-top: 20px;
}
</style>
admin/src/views/combo/components/config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
export const pickerOptions = { // ç²¾ç¡®åˆ°ç§’
  disabledDate(time) {
    return time.getTime() < new Date(new Date().toLocaleDateString()).getTime()
  },
  shortcuts: [
    {
      text: '7天内失效',
      onClick(picker) {
        const end = new Date()
        const start = new Date()
        end.setTime(start.getTime() + 3600 * 1000 * 24 * 7)
        picker.$emit('pick', [start, end])
      }
    },
    {
      text: '30天内失效',
      onClick(picker) {
        const end = new Date()
        const start = new Date()
        end.setTime(start.getTime() + 3600 * 1000 * 24 * 30)
        picker.$emit('pick', [start, end])
      }
    },
    {
      text: '90天内失效',
      onClick(picker) {
        const end = new Date()
        const start = new Date()
        end.setTime(start.getTime() + 3600 * 1000 * 24 * 90)
        picker.$emit('pick', [start, end])
      }
    }
  ]
}
admin/src/views/combo/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
<template>
  <div class="main_app">
    <QueryForm v-model="filters" :query-form-config="queryFormConfig" @handleQuery="getList(1)" @clear="clear" />
    <div class="table_btns">
      <el-button type="primary" @click="handleEdit()">新增</el-button>
    </div>
    <el-table v-loading="loading" :data="list" stripe border>
      <el-table-column prop="code" label="套餐名称" align="center" min-width="120" show-overflow-tooltip />
      <el-table-column prop="" label="套餐类型" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="次数" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="有效期" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="ä»·æ ¼" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="总发行数量" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="已售售量" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="销售渠道" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="销售时段" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" label="状态" align="center" min-width="100" show-overflow-tooltip />
      <el-table-column label="操作" fixed="right" align="center" min-width="80" show-overflow-tooltip>
        <template v-slot="{ row }">
          <span @click="handleDetail(row)" class="primaryColor pointer">查看详情</span>
        </template>
      </el-table-column>
    </el-table>
    <div class="table_btns">
      <Pagination @size-change="handleSizeChange" @current-change="getList" :pagination="pagination" />
    </div>
    <!--  -->
    <Edit v-if="isShowEdit" @close="isShowEdit = false" @success="getList" ref="EditRef" />
    <ComboDetail v-if="isShowDetail" ref="DetailRef" />
  </div>
</template>
<script>
import BasePageTemp from '@/components/base/BasePageTemp'
import TableLayout from '@/layouts/TableLayout'
import Edit from './components/Edit'
import ComboDetail from './components/ComboDetail.vue'
export default {
  extends: BasePageTemp,
  components: {
    TableLayout,
    Edit,
    ComboDetail
  },
  data() {
    return {
      loading: false,
      isShowEdit: false,
      isShowDetail: false,
      queryFormConfig: {
        formItems: [
          {
            filed: 'name',
            type: 'input',
            label: '套餐名称',
          },
          {
            filed: 'type',
            type: 'select',
            label: '适用项目',
            labelCode: 'name',
            valueCode: 'id',
            options: []
          },
          {
            filed: 'status',
            type: 'select',
            label: '状态',
            options: []
          },
        ],
        online: true
      },
      list: [{}]
    }
  },
  created() {
    // this.getList()
    // this.initData()
  },
  methods: {
    handleSub() {
      this.$refs.ruleForm.validate((valid) => {
        if (valid) {
          alert('submit!')
        }
      })
    },
    handleEdit() {
      this.isShowEdit = true
      this.$nextTick(() => {
        this.$refs.EditRef.isShowModal = true
      })
    },
    handleDetail(row) {
      this.isShowDetail = true
      this.$nextTick(() => {
        this.$refs.DetailRef.isShowModal = true
        // this.$refs.DetailRef.getDetail(row.id)
      })
    },
    handleEx() {
      this.$dialog.exportConfirm('确认导出吗?')
        .then(() => {
          this.loading = true
          ywOutinboundEx({
            page: this.pagination.page,
            capacity: 1000000,
            model: this.filters
          })
            .then(response => {
              this.download(response)
            })
            .catch(e => {
              this.$tip.apiFailed(e)
            })
            .finally(() => {
              this.loading = false
            })
        })
        .catch(() => { })
    },
    initData() {
      getStoreList({ capacity: 9999, page: 1, model: {} }).then(res => {
        this.queryFormConfig.formItems[1].options = res.records || []
      })
    },
    getList(page) {
      const { pagination, filters } = this
      this.loading = true
      if (page) { pagination.page = page }
      ywOutinboundPage({
        model: {
          ...filters,
          inOut: 0
        },
        // sorts: [{ direction: 'DESC', property: 'param1' }],
        capacity: pagination.pageSize,
        page: page,
      }).then(res => {
        this.loading = false
        this.list = res.records || []
        this.list.forEach(item => {
          item.typeName = this.StoreTypeOps[item.type].name
        })
        this.pagination.total = res.total || 0
      }, () => {
        this.loading = false
      })
    },
    clear() {
      this.filters = { inOut: 0 }
      this.pagination.pageSize = 10
      this.pagination.page = 1
      this.getList()
    },
    handleSizeChange(capacity) {
      this.pagination.pageSize = capacity
      this.getList()
    }
  }
}
</script>
<style></style>
admin/src/views/combo/order.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,156 @@
<template>
  <div class="main_app">
    <Breadcrumb />
    <QueryForm v-model="filters" :query-form-config="queryFormConfig" @handleQuery="getList(1)" @clear="clear" />
    <div class="table_btns">
      <el-button type="primary" @click="handleEx()">导出</el-button>
    </div>
    <el-table v-loading="loading" :data="list" stripe border>
      <el-table-column prop="code" align="center" label="订单编号" min-width="140" show-overflow-tooltip>
        <template scope="{row}">
          <span class="primaryColor pointer">{{ row.orderId }}</span>
        </template>
      </el-table-column>
      <el-table-column prop="" align="center" label="套餐卡" min-width="120" show-overflow-tooltip />
      <el-table-column prop="" align="center" label="合计(元)" min-width="80" show-overflow-tooltip />
      <el-table-column prop="" align="center" label="实付(元)" min-width="80" show-overflow-tooltip />
      <el-table-column prop="" align="center" label="已退金额(元)" min-width="100" show-overflow-tooltip />
      <el-table-column prop="" align="center" label="用户信息" min-width="120" show-overflow-tooltip />
      <el-table-column prop="" align="center" label="订单状态" min-width="110" show-overflow-tooltip />
      <el-table-column label="操作" fixed="right" align="center" min-width="80" show-overflow-tooltip>
        <template v-slot="{ row }">
          <span @click="handleDetail(row.id)" v-permissions="['business:ywoutinboundrecord:query']"
            class="primaryColor pointer">查看详情</span>
        </template>
      </el-table-column>
    </el-table>
    <div class="table_btns">
      <Pagination @size-change="handleSizeChange" @current-change="getList" :pagination="pagination" />
    </div>
    <!--  -->
    <OrderDetail v-if="isShowDetail" ref="OrderDetailRef" />
  </div>
</template>
<script>
import BasePageTemp from '@/components/base/BasePageTemp'
import Breadcrumb from '@/layouts/Breadcrumb'
import OrderDetail from './components/OrderDetail.vue'
export default {
  extends: BasePageTemp,
  components: {
    Breadcrumb,
    OrderDetail
  },
  data() {
    return {
      loading: false,
      isShowDetail: false,
      queryFormConfig: {
        formItems: [
          {
            filed: 'orderId',
            type: 'input',
            label: '订单编号',
          },
          {
            filed: 'name',
            type: 'input',
            label: '套餐卡',
            placeholder: '请输入卡名称',
          },
          {
            filed: 'pay',
            type: 'select',
            label: '支付方式',
            options: []
          },
          {
            filed: 'status',
            type: 'select',
            label: '订单状态',
            options: []
          },
          {
            filed: 'time',
            type: 'date',
            label: '支付时间',
          },
          {
            filed: 'username',
            type: 'input',
            label: '用户信息',
          },
        ],
        online: true
      }
    }
  },
  created() {
    // this.getList()
    // this.initData()
  },
  methods: {
    handleDetail(id) {
      getDetail(id)
        .then(res => {
          this.$refs.OrderDetailRef.open('订单详情', res)
        })
        .catch(err => {
          this.$tip.apiFailed(err)
        })
    },
    handleEx() {
      this.$dialog.exportConfirm('确认导出吗?')
        .then(() => {
          this.loading = true
          ywOutinboundEx({
            page: this.pagination.page,
            capacity: 1000000,
            model: this.filters
          })
            .then(response => {
              this.download(response)
            })
            .catch(e => {
              this.$tip.apiFailed(e)
            })
            .finally(() => {
              this.loading = false
            })
        })
        .catch(() => { })
    },
    initData() {
      getStoreList({ capacity: 9999, page: 1, model: {} }).then(res => {
        this.queryFormConfig.formItems[1].options = res.records || []
      })
    },
    getList(page) {
      const { pagination, filters } = this
      this.loading = true
      if (page) { pagination.page = page }
      ywOutinboundPage({
        model: {
          ...filters,
          inOut: 0
        },
        // sorts: [{ direction: 'DESC', property: 'param1' }],
        capacity: pagination.pageSize,
        page: page,
      }).then(res => {
        this.loading = false
        this.list = res.records || []
        this.list.forEach(item => {
          item.typeName = this.StoreTypeOps[item.type].name
        })
        this.pagination.total = res.total || 0
      }, () => {
        this.loading = false
      })
    },
  }
}
</script>
<style></style>
admin/src/views/combo/record.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,494 @@
<template>
  <div class="main_app">
    <Breadcrumb />
    <QueryForm v-model="querys" :query-form-config="queryFormConfig" @handleQuery="getList(1)" @clear="clearQueryForm">
      <template #btns>
        <el-button v-if="meta.indexOf('MealsUseDetailExport') > -1" type="primary"
          @click="comboRecordExport">导出</el-button>
      </template>
      <template #indate>
        <el-date-picker v-model="querys.indate" type="datetimerange" range-separator="至" start-placeholder="开始日期"
          end-placeholder="结束日期" format="yyyy-MM-dd HH:mm:ss" value-format="yyyy-MM-dd HH:mm:ss" class="w400"
          :picker-options="pickerOptions" />
      </template>
    </QueryForm>
    <div class="table_btns">
      <el-button v-preventReClick plain type="primary" @click="handleEx">导出</el-button>
      <el-button v-preventReClick plain type="danger" @click="openModal('zuofei')">作废</el-button>
      <el-button v-preventReClick plain @click="openModal('tiaozheng')">套餐调整</el-button>
    </div>
    <el-table v-loading="loading" :data="list" stripe border @selection-change="handleSelectionChange">
      <el-table-column fixed="left" align="center" type="selection" :selectable="handleDisable" width="55" />
      <el-table-column align="center" label="套餐票号" width="300" show-overflow-tooltip>
        <template v-slot="scope">
          <span class="primaryColor pointer" @click="comboDetail(scope.row)">{{
            scope.row.id
            }}</span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="套餐类型" width="120">
        <template v-slot="scope">
          <span v-if="scope.row.mealsType === '0'">门票次卡</span>
          <span v-if="scope.row.mealsType === '1'">门票期限卡</span>
          <span v-if="scope.row.mealsType === '2'">预定次卡</span>
          <span v-if="scope.row.mealsType === '3'">课程预约次卡</span>
          <span v-if="scope.row.mealsType === '4'">课程期限卡</span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="套餐名称" show-overflow-tooltip width="160" prop="mealsName" />
      <el-table-column align="center" label="用户信息" min-width="240" prop="memberInfo" show-overflow-tooltip />
      <el-table-column align="center" label="使用次数" width="80" prop="useCount">
        <template v-slot="scope">
          <span v-if="scope.row.mealsType === '1'">-</span>
          <span v-else>{{ scope.row.useCount }}</span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="有效日期" width="80" prop="remainCount">
        <template v-slot="scope">
          <span v-if="scope.row.mealsType === '1'">-</span>
          <span v-else>{{ scope.row.remainCount }}</span>
        </template>
      </el-table-column>
      <el-table-column fixed="right" align="center" label="套餐状态" width="80">
        <template v-slot="scope">
          <span v-if="scope.row.status === '0'" class="text_success">正常</span>
          <span v-if="scope.row.status === '1'" class="text_warning">冻结</span>
          <span v-if="scope.row.status === '2'" class="text-danger">作废</span>
          <span v-if="scope.row.status === '3'" class="text-danger">失效</span>
        </template>
      </el-table-column>
      <el-table-column align="center" label="有效日期" width="120" prop="validTime" />
      <el-table-column align="center" label="操作" fixed="right" width="150">
        <template v-slot="scope">
          <template>
            <el-button v-if="
              meta.indexOf('MealsMemberPartRefund') > -1 &&
              (scope.row.status == '0' || scope.row.status == '1')
            " type="text" @click="rowClickRefund(scope.row)">退款</el-button>
            <el-button v-if="scope.row.status == '0'" type="text"
              @click="getServiceChargePriceBtn('2', scope.row.id)">冻结</el-button>
          </template>
        </template>
      </el-table-column>
    </el-table>
    <div class="table_btns">
      <Pagination @size-change="handleSizeChange" @current-change="getList" :pagination="pagination" />
    </div>
    <el-dialog title="套餐调整" :visible.sync="isShowAdjust" width="500px">
      <div class="adjust_modal">
        <div style="margin-top: -30px; margin-bottom: 10px;">
          <el-radio v-model="adjustData.aa" label="1">已选当前2条数据</el-radio>
        </div>
        <div style="margin-bottom: 16px;">
          <el-radio v-model="adjustData.aa" label="1">已选现有筛选条件下全部的11条数据</el-radio>
        </div>
        <div class="df_ac mb5">
          <span class="key">有效期增加:</span>
          <el-input v-model="adjustData.addTime" oninput="value=value.replace(/[^\d]/g,'')" class="flex1 mr10" />
          <span>天</span>
        </div>
        <div class="df_ac mb20">
          <span class="key" />
          <span class="text_warning">修改后套餐有效期增加以上天数</span>
        </div>
        <div class="df_ac mt20">
          <span class="key">调整备注:</span>
          <el-input v-model="ModelRemark" style="width: 356px" placeholder="请按要求输入备注说明,非必填" />
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="isShowAdjust = false">取消</el-button>
        <el-button v-preventReClick type="primary" :loading="subLoading" @click="handleModelEnter">确定</el-button>
      </span>
    </el-dialog>
    <Refund v-if="isShowRefund" ref="RefundRef" @close="isShowRefund = false" @success="refundSuccess" />
    <Detail v-if="isShowDetail" ref="detailRef" />
    <orderDialog ref="child" @orderSuccess="getList()" />
  </div>
</template>
<script>
import { pickerOptions } from './components/config'
import BasePageTemp from '@/components/base/BasePageTemp'
import Breadcrumb from '@/layouts/Breadcrumb'
// import Detail from '../comboDetail'
// import Refund from './refund.vue'
export default {
  name: 'SalesRecord',
  extends: BasePageTemp,
  components: { Breadcrumb },
  data() {
    return {
      exportLoading: false,
      isShowDetail: false,
      isShowRefund: false,
      queryFormConfig: {
        formItems: [{
          filed: 'id',
          type: 'input',
          label: '套餐票号',
          placeholder: '请输入套餐号',
          clearable: true
        }, {
          filed: 'mealsName',
          type: 'input',
          label: '套餐名称',
          placeholder: '请输入套餐名称',
          clearable: true
        }, {
          filed: 'memberSearchValue',
          type: 'input',
          label: '用户信息',
          clearable: true
        }, {
          filed: 'status',
          type: 'select',
          label: '状态',
          placeholder: '请选择状态',
          clearable: true,
          options: [
            { value: '0', label: '正常' },
            { value: '1', label: '冻结' },
            { value: '2', label: '作废' },
            { value: '3', label: '失效' }]
        }],
        online: true
      },
      pickerOptions,
      querys: {
        id: '',
        mealsName: '',
        memberSearchValue: '',
        status: '',
        indate: []
      },
      saleRecordList: [],
      saleRecordTotal: 0,
      selectSalesList: [],
      saleRecordLoading: false,
      subLoading: false,
      isShowModal: false,
      ModalTitle: '',
      ModalText: '',
      ModelRemark: '',
      // â¬‡ï¸adjust调整相关
      isShowAdjust: false,
      adjustData: {
        addNum: '',
        addTime: ''
      },
      serviceChargePrice: '',
      serviceChargeId: ''
    }
  },
  created() {
    // this.meta = this.$route.meta.buttons || []
    // this.getList(1)
  },
  methods: {
    rowClickRefund(row) {
      this.isShowRefund = true
      this.$nextTick(() => {
        this.$refs.RefundRef.getPrice(row)
        this.$refs.RefundRef.isShow = true
      })
    },
    refundSuccess() {
      this.isShowRefund = false
      this.getList()
    },
    getServiceChargePriceBtn(type, mealsMemberId) {
      this.serviceChargeId = mealsMemberId
      getServiceChargePrice({
        param: {
          type,
          mealsMemberId
        }
      }).then((res) => {
        if (res.errorCode === '000000') {
          this.serviceChargePrice = res.record.price
          this.openModal('dongjie')
        }
      })
        .catch(() => { })
    },
    handleEx() {
      const { querys } = this
      this.exportLoading = true
      recordExport({
        param: {
          ...querys
        }
      }).then((res) => {
        this.exportLoading = false
        if (res.errorCode === '000000') {
          const a = document.createElement('a') // åˆ›å»ºä¸€ä¸ªa标签元素
          a.style.display = 'none' // è®¾ç½®å…ƒç´ ä¸å¯è§
          a.href = res.record.showUrl // è®¾ç½®ä¸‹è½½åœ°å€
          document.body.appendChild(a) // åŠ å…¥
          a.click() // è§¦å‘点击,下载
          document.body.removeChild(a) //
          this.$message.success('导出成功')
        }
      })
        .catch(() => {
          this.exportLoading = false
        })
    },
    handleDisable(row, index) {
      if (row.status === '2') {
        return false
      } else {
        return true
      }
    },
    // æŸ¥è¯¢è¡¨æ ¼æ•°æ®
    getList(page) {
      const { querys, pagination } = this
      if (page) {
        pagination.page = page
        this.pagination.page = Number(page)
      }
      if (querys.indate && querys.indate.length > 0) {
        querys.startTime = querys.indate[0]
        querys.endTime = querys.indate[1]
      } else {
        querys.startTime = null
        querys.endTime = null
      }
      pagination.firstQueryTime = parseTime(new Date())
      this.saleRecordLoading = true
      comboSalesRecordPost({
        pagination, param: {
          ...querys,
          venueId: sessionStorage.getItem('venueId')
        }
      }).then(res => {
        this.saleRecordLoading = false
        if (res.errorCode === '000000') {
          this.saleRecordLoading = false
          this.saleRecordTotal = res.totalCount
          this.saleRecordList = res.recordList
          if (res.totalCount && res.recordList.length === 0 && pagination.page > 1) {
            this.getList(Math.ceil(res.totalCount / pagination.rows))
          }
        }
      }).catch(() => {
        this.saleRecordLoading = false
      })
    },
    clearQueryForm() {
      // eslint-disable-next-line no-unused-vars
      this.querys = {}
      this.getList(1)
    },
    // æ‰“å¼€modal å†»ç»“ è§£å†» ä½œåºŸ
    openModal(type) {
      const { selectSalesList } = this
      this.ModelRemark = ''
      this.adjustData.addNum = ''
      this.adjustData.addTime = ''
      switch (type) {
        case 'dongjie':
          this.ModalTitle = '套餐冻结'
          this.ModalText = '确定冻结选中套餐吗?冻结后,套餐将无法使用'
          break
        case 'jiedong':
          this.ModalTitle = '套餐解冻'
          this.ModalText = '确定解冻选中套餐吗?解冻后,套餐可立即生效使用'
          break
        case 'zuofei':
          this.ModalTitle = '套餐作废'
          this.ModalText = '确定作废选中套餐吗?作废后,套餐不可使用'
          break
        case 'tiaozheng':
          this.ModalTitle = '套餐调整'
          break
        default:
          break
      }
      if (selectSalesList.length === 0 && type !== 'dongjie') {
        return this.$message.warning(`请先选择要${this.ModalTitle.slice(2)}的套餐`)
      }
      if (this.ModalTitle === '套餐调整') {
        this.isShowAdjust = true
      } else {
        this.isShowModal = true
      }
    },
    // ç¡®å®š å†»ç»“ è§£å†» ä½œåºŸ
    handleModelEnter() {
      // if (!this.ModelRemark) {
      //   this.$message.warning('操作备注必填')
      //   return
      // }
      switch (this.ModalTitle) {
        case '套餐冻结':
          this.Freeze()
          break
        case '套餐解冻':
          this.Unfreeze()
          break
        case '套餐作废':
          this.cancellation()
          break
        case '套餐调整':
          this.handleAdjust()
          break
        default:
          break
      }
    },
    // å†»ç»“
    Freeze() {
      const that = this
      const data = {
        type: '1',
        handleRemake: this.ModelRemark,
        isCreateOrder: '1',
        id: this.serviceChargeId
      }
      this.subLoading = true
      handleComboSalesPost({ param: { ...data } }).then(res => {
        this.subLoading = false
        this.isShowModal = false
        if (res.errorCode === '000000') {
          const obj = res.record
          if (obj && obj.price) {
            obj.goodsInfos = obj.serviceChargeOrderInfo
            obj.allPrice = obj.price
            this.$refs.child.continuePay(obj, 'handlingFees')
          } else {
            this.$message.success('冻结成功')
            this.getList()
          }
          //
        } else if (res.errorCode === '200001') {
          this.$confirm('存在未处理订单, æ˜¯å¦è·³è½¬?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
            callback: () => { },
            beforeClose: (action, ctx, close) => {
              if (action !== 'confirm') {
                close()
                return
              }
              ctx.confirmButtonLoading = true
              that.$router.push({ name: 'HandlingFees' })
            }
          }).finally(() => {
            this.subLoading = false
            this.isShowModal = false
          })
        }
      })
    },
    // è§£å†»
    async Unfreeze() {
      const data = {
        type: '2',
        handleRemake: this.ModelRemark,
        id: this.selectSalesList.map(i => i.id).join(',')
      }
      this.subLoading = true
      const res = await handleComboSalesPost({ param: { ...data } }).catch(() => {
        this.subLoading = false
        this.isShowModal = false
      })
      this.subLoading = false
      this.isShowModal = false
      if (res && res.errorCode === '000000') {
        this.$message.success('解冻成功')
        this.getList()
      }
    },
    // è°ƒæ•´
    async handleAdjust() {
      if (!this.adjustData.addNum && !this.adjustData.addTime) {
        return this.$message.warning('请输入要调整的有效期天数或者余量次数')
      }
      const data = {
        type: '3',
        handleRemake: this.ModelRemark,
        addNum: this.adjustData.addNum || 0,
        addTime: this.adjustData.addTime || 0,
        id: this.selectSalesList.map(i => i.id).join(',')
      }
      this.subLoading = true
      const res = await handleComboSalesPost({ param: { ...data } }).catch(() => {
        this.subLoading = false
        this.isShowModal = false
      })
      this.subLoading = false
      this.isShowModal = false
      if (res && res.errorCode === '000000') {
        this.$message.success('套餐调整成功')
        this.getList()
        this.isShowAdjust = false
      }
    },
    // ä½œåºŸ
    async cancellation() {
      const data = {
        type: '4',
        handleRemake: this.ModelRemark,
        id: this.selectSalesList.map(i => i.id).join(',')
      }
      this.subLoading = true
      const res = await handleComboSalesPost({ param: { ...data } }).catch(() => {
        this.subLoading = false
        this.isShowModal = false
      })
      this.subLoading = false
      this.isShowModal = false
      if (res && res.errorCode === '000000') {
        this.$message.success('作废成功')
        this.getList()
      }
    },
    handleSelectionChange(val) {
      this.selectSalesList = val
    },
    comboDetail(item) {
      this.isShowDetail = true
      this.$nextTick(() => {
        this.$refs.detailRef.isShowModal = true
        this.$refs.detailRef.comboDetail(item)
      })
      // this.$router.push({
      //   name: 'comboDetail',
      //   query: item
      // })
    },
    pageSizeChange(val) {
      this.pagination.rows = val
      this.getList(1)
    }
  }
}
</script>
<style lang="scss" scoped>
.adjust_modal {
  .df_ac {
    .key {
      width: 92px;
      text-align: right;
    }
  }
  .el-dialog__body{
  }
  .text_warning {
    color: #e89e42;
  }
}
</style>
admin/src/views/operation/components/Map.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
<template>
  <GlobalWindow title="绘制地图停车区域" :visible.sync="isShowModal" :confirm-working="isWorking" @confirm="confirm"
    width="1000px">
    <div class="map_title">站点名称 åœè½¦åŒºåŸŸç»˜åˆ¶</div>
    <div id="mapContainer" style="width: 100%; height: calc( 100% - 40px )"></div>
  </GlobalWindow>
</template>
<script>
import GlobalWindow from '@/components/common/GlobalWindow'
export default {
  components: {
    GlobalWindow
  },
  data() {
    return {
      isShowModal: false,
      isWorking: false,
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initMap()
    })
  },
  methods: {
    initMap() {
      this.map = new TMap.Map(document.getElementById("mapContainer"), {
        zoom: 14, // è®¾ç½®åœ°å›¾ç¼©æ”¾çº§åˆ«
        center: new TMap.LatLng(30.656964, 104.066186), // è®¾ç½®åœ°å›¾ä¸­å¿ƒç‚¹åæ ‡
      })
      var polygon = new TMap.MultiPolygon({
        map: this.map,
      })
      this.editor = new TMap.tools.GeometryEditor({
        // TMap.tools.GeometryEditor æ–‡æ¡£åœ°å€ï¼šhttps://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor
        map: this.map, // ç¼–辑器绑定的地图对象
        overlayList: [
          // å¯ç¼–辑图层 æ–‡æ¡£åœ°å€ï¼šhttps://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#4
          {
            overlay: polygon,
            id: "polygon",
          },
        ],
        actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW, // ç¼–辑器的工作模式
        activeOverlayId: this.activeId, // æ¿€æ´»å›¾å±‚
        snappable: true, // å¼€å¯å¸é™„
      })
      // ç›‘听绘制结束事件,获取绘制几何图形
      this.editor.on("draw_complete", (geometry) => {
        console.log('所有顶点坐标集合', geometry)
        // å¤šè¾¹å½¢å¤„理
        if (this.activeId === "polygon") {
          console.log("得到顶点经纬度的数组", geometry.paths)
        }
      })
    },
    confirm() { }
  }
}
</script>
<style lang="scss" scoped>
.map_title {
  font-size: 16px;
  font-weight: 500;
  margin-bottom: 10px;
}
#mapContainer {
}
</style>
admin/src/views/operation/components/SitesWindow.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
<template>
  <GlobalWindow
    :title="title"
    :visible.sync="visible"
    :confirm-working="isWorking"
    @confirm="confirm"
    width="600px"
  >
    <el-form :model="form" ref="form" :rules="rules" label-width="100px">
      <el-form-item label="站点编号" prop="code">
        <el-input v-model="form.code" :disabled="form.id" placeholder="请输入站点编号,全局唯一" v-trim/>
      </el-form-item>
      <el-form-item label="站点名称" prop="name">
        <el-input v-model="form.name" placeholder="请输入站点名称" v-trim/>
      </el-form-item>
      <el-form-item label="是否异常上报" prop="needNotice">
        <el-radio-group v-model="form.needNotice">
          <el-radio :label="0">上报</el-radio>
          <el-radio :label="1">不上报</el-radio>
        </el-radio-group>
      </el-form-item>
      <!-- <el-form-item label="站点ip" prop="ip">
        <el-input v-model="form.ip" placeholder="请输入站点ip地址" v-trim/>
      </el-form-item>
      <el-form-item label="站点端口号" prop="port">
        <el-input v-model="form.port" placeholder="请输入站点端口号" v-trim/>
      </el-form-item> -->
      <!-- <el-form-item label="备注" prop="info">
        <el-input v-model="form.info" placeholder="请输入备注" v-trim/>
      </el-form-item> -->
    </el-form>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
export default {
  name: 'OperaSitesWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  data () {
    return {
      // è¡¨å•数据
      form: {
        id: null,
        name: '',
        code: '',
        needNotice:0,
        ip: '',
        port: ''
        // createDate: '',
        // creator: '',
        // editDate: '',
        // editor: '',
        // isdeleted: '',
        // info: '',
        // name: '',
        // code: '',
        // status: '',
        // lockNum: '',
      },
      // éªŒè¯è§„则
      rules: {
        code: [
          { required: true, message: '请输入站点编号', tigger: 'blur' },
        ],
        name: [
          { required: true, message: '请输入站点名称', tigger: 'blur' },
        ],
        ip: [
          { required: true, message: '请输入站点ip地址', tigger: 'blur' },
        ],
        port: [
          { required: true, message: '请输入站点端口号', tigger: 'blur' },
        ],
      }
    }
  },
  created () {
    this.config({
      api: '/business/sites',
      'field.id': 'id'
    })
  }
}
</script>
admin/src/views/operation/components/TramEdit.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
<template>
  <GlobalWindow
    :title="title"
    :visible.sync="visible"
    :confirm-working="isWorking"
    @confirm="confirm"
    width="600px"
  >
    <el-form :model="form" ref="form" :rules="rules" label-width="100px">
      <el-form-item label="车辆类型" prop="code">
        <el-select  v-model="form.named" placeholder="请选择">
          <el-option label="3人电动车" :value="0"></el-option>
          <el-option label="4人电动车" :value="1"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="车辆编号" prop="code">
        <el-input v-model="form.name" placeholder="请输入车辆编号" v-trim/>
      </el-form-item>
      <el-form-item label="控制器SN" prop="needNotice">
        <el-input v-model="form.name" placeholder="请输入控制器SN" v-trim/>
      </el-form-item>
    </el-form>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
export default {
  name: 'OperaSitesWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  data () {
    return {
      // è¡¨å•数据
      form: {
        id: null,
        name: '',
        code: '',
        needNotice:0,
        ip: '',
        port: ''
      },
      // éªŒè¯è§„则
      rules: {
        code: [
          { required: true, message: '请输入站点编号', tigger: 'blur' },
        ],
        name: [
          { required: true, message: '请输入站点名称', tigger: 'blur' },
        ],
        ip: [
          { required: true, message: '请输入站点ip地址', tigger: 'blur' },
        ],
        port: [
          { required: true, message: '请输入站点端口号', tigger: 'blur' },
        ],
      }
    }
  },
  created () {
    this.config({
      api: '/business/sites',
      'field.id': 'id'
    })
  }
}
</script>
admin/src/views/operation/site.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
<template>
  <TableLayout :permissions="['business:sites:query']">
    <!-- æœç´¢è¡¨å• -->
    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
      <el-form-item label="站点编号" prop="code">
        <el-input v-model="searchForm.code" placeholder="请输入站点编号" @keypress.enter.native="search"></el-input>
      </el-form-item>
      <el-form-item label="站点名称" prop="name">
        <el-input v-model="searchForm.name" placeholder="请输入站点名称" @keypress.enter.native="search"></el-input>
      </el-form-item>
      <section>
        <el-button type="primary" @click="search">搜索</el-button>
        <el-button @click="reset">重置</el-button>
      </section>
    </el-form>
    <!-- è¡¨æ ¼å’Œåˆ†é¡µ -->
    <template v-slot:table-wrap>
      <ul class="toolbar" v-permissions="['business:sites:create']">
        <li><el-button type="primary" @click="$refs.operaSitesWindow.open('新建站点')" icon="el-icon-plus" v-permissions="['business:sites:create']">新建</el-button></li>
      </ul>
      <el-table
        v-loading="isWorking.search"
        :data="tableData.list"
        stripe
        border
      >
        <el-table-column prop="code" label="站点编号"  :sort-method="(a,b)=>{ return   a.code-b.code}"  sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="站点名称" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="createTime" label="创建时间" sortable  min-width="100px" align="center"></el-table-column>
        <el-table-column
          v-if="containPermissions(['business:sites:update', 'business:sites:delete'])"
          label="操作"
          min-width="120"
          align="center"
          fixed="right"
        >
          <template slot-scope="{row}">
            <el-button type="text" @click="handleDraw(row)">绘制地图区域</el-button>
            <el-button type="text" @click="$refs.operaSitesWindow.open('编辑站点', row)" icon="el-icon-edit" v-permissions="['business:sites:update']">编辑</el-button>
            <el-button type="text" @click="deleteById(row)" icon="el-icon-delete" v-permissions="['business:sites:delete']">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        @size-change="handleSizeChange"
        @current-change="handlePageChange"
        :pagination="tableData.pagination"
      >
      </pagination>
    </template>
    <!-- æ–°å»º/修改 -->
    <OperaSitesWindow ref="operaSitesWindow" @success="handlePageChange" />
    <!--  æŸ¥çœ‹äºŒç»´ç   -->
    <AMap v-if="isShowMap" ref="MapRef" />
  </TableLayout>
</template>
<script>
import BaseTable from '@/components/base/BaseTable'
import TableLayout from '@/layouts/TableLayout'
import Pagination from '@/components/common/Pagination'
import OperaSitesWindow from './components/SitesWindow'
import AMap from './components/Map.vue'
export default {
  name: 'Sites',
  extends: BaseTable,
  components: { TableLayout, Pagination, OperaSitesWindow, AMap },
  data () {
    return {
      // æœç´¢
      searchForm: {
        id: '',
        createDate: '',
        creator: '',
        editDate: '',
        editor: '',
        isdeleted: '',
        info: '',
        name: '',
        code: '',
        status: '',
        lockNum: '',
        ip: '',
        clientVersion: ''
      },
      isShowMap: false
    }
  },
  created () {
    this.config({
      module: '站点信息表',
      api: '/business/sites',
      'field.id': 'id',
      'field.main': 'id'
    })
    this.search()
  },
  methods: {
    handleDraw() {
      this.isShowMap = true
      this.$nextTick(() => {
        this.$refs.MapRef.isShowModal = true
      })
    }
  }
}
</script>
admin/src/views/operation/tram.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
<template>
  <TableLayout :permissions="['business:sites:query']">
    <!-- æœç´¢è¡¨å• -->
    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
      <el-form-item label="车辆编号" prop="code">
        <el-input v-model="searchForm.code" placeholder="请输入车辆编号" @keypress.enter.native="search"></el-input>
      </el-form-item>
      <el-form-item label="所在站点" prop="name">
        <el-input v-model="searchForm.name" placeholder="请输入名称/编码" @keypress.enter.native="search"></el-input>
      </el-form-item>
      <el-form-item label="电量情况" prop="name">
        <el-select v-model="searchForm.status" placeholder="电量情况" @change="search">
          <el-option label="正常" value="0" />
          <el-option label="电量低" value="1" />
        </el-select>
      </el-form-item>
      <section>
        <el-button type="primary" @click="search">搜索</el-button>
        <el-button @click="reset">重置</el-button>
      </section>
    </el-form>
    <!-- è¡¨æ ¼å’Œåˆ†é¡µ -->
    <template v-slot:table-wrap>
      <ul class="toolbar" v-permissions="['business:sites:create']">
        <li><el-button type="primary" @click="$refs.TramEditRef.open('新增车辆')" icon="el-icon-plus" v-permissions="['business:sites:create']">新建</el-button></li>
      </ul>
      <el-table
        v-loading="isWorking.search"
        :data="tableData.list"
        stripe
        border
      >
        <el-table-column prop="code" label="车辆编码"  :sort-method="(a,b)=>{ return   a.code-b.code}"  sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="车辆类型" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="控制器SN" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="当前电压值" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="电量情况" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="站点编号" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="name" label="站点名称" sortable min-width="100px" align="center"></el-table-column>
        <el-table-column prop="createTime" label="最后通讯时间" sortable  min-width="100px" align="center"></el-table-column>
        <el-table-column
          v-if="containPermissions(['business:sites:update', 'business:sites:delete'])"
          label="操作"
          min-width="160"
          align="center"
          fixed="right"
        >
          <template slot-scope="{row}">
            <el-button type="text" @click="$refs.QRcode.open('查看二维码', row.code)" icon="el-icon-picture-outline-round">查看二维码</el-button>
            <el-button type="text" @click="$refs.TramEditRef.open('编辑车辆', row)" icon="el-icon-edit" v-permissions="['business:sites:update']">编辑</el-button>
            <el-button type="text" @click="deleteById(row)" icon="el-icon-delete" v-permissions="['business:sites:delete']">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        @size-change="handleSizeChange"
        @current-change="handlePageChange"
        :pagination="tableData.pagination"
      >
      </pagination>
    </template>
    <!-- æ–°å»º/修改 -->
    <TramEdit ref="TramEditRef" @success="handlePageChange"/>
    <!--  æŸ¥çœ‹äºŒç»´ç   -->
    <QRcode ref="QRcode" />
  </TableLayout>
</template>
<script>
import BaseTable from '@/components/base/BaseTable'
import TableLayout from '@/layouts/TableLayout'
import Pagination from '@/components/common/Pagination'
import TramEdit from './components/TramEdit.vue'
import QRcode from '@/components/business/QRcode'
export default {
  name: 'Sites',
  extends: BaseTable,
  components: { TableLayout, Pagination, TramEdit, QRcode },
  data () {
    return {
      // æœç´¢
      searchForm: {
        id: '',
        createDate: '',
        creator: '',
        editDate: '',
        editor: '',
        isdeleted: '',
        info: '',
        name: '',
        code: '',
        status: '',
        lockNum: '',
        ip: '',
        clientVersion: ''
      }
    }
  },
  created () {
    this.config({
      module: '站点信息表',
      api: '/business/sites',
      'field.id': 'id',
      'field.main': 'id'
    })
    this.search()
  }
}
</script>
admin/yarn.lock
ÎļþÒÑɾ³ý
server/platform/src/main/java/com/doumee/api/business/DiscountMemberController.java
@@ -7,6 +7,7 @@
import com.doumee.core.model.PageWrap;
import com.doumee.core.model.PageData;
import com.doumee.dao.business.model.DiscountMember;
import com.doumee.dao.business.web.request.DiscountMemberDTO;
import com.doumee.service.business.DiscountMemberService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -85,6 +86,24 @@
    @GetMapping("/{id}")
    @RequiresPermissions("business:discountmember:query")
    public ApiResponse findById(@PathVariable String id) {
        return ApiResponse.success(discountMemberService.findById(id));
        return ApiResponse.success(discountMemberService.getDetail(id));
    }
    @ApiOperation("套餐卡报废")
    @PostMapping("/cancel")
    @RequiresPermissions("business:discountmember:update")
    public ApiResponse cancel(@RequestBody DiscountMemberDTO model) {
        discountMemberService.cancel(model);
        return ApiResponse.success(null);
    }
    @ApiOperation("套餐卡调整")
    @PostMapping("/adjust")
    @RequiresPermissions("business:discountmember:update")
    public ApiResponse adjust(@RequestBody DiscountMemberDTO model) {
        discountMemberService.adjust(model);
        return ApiResponse.success(null);
    }
}
server/platform/src/main/java/com/doumee/api/business/GoodsorderController.java
@@ -10,11 +10,13 @@
import com.doumee.core.utils.DateUtil;
import com.doumee.dao.business.model.Goodsorder;
import com.doumee.dao.business.model.WxBillDetail;
import com.doumee.dao.business.vo.DiscountGoodsorderExportVO;
import com.doumee.dao.business.vo.GoodsorderExportVO;
import com.doumee.dao.business.vo.GoodsorderTotalDataVO;
import com.doumee.dao.business.web.request.GoodsorderBackDTO;
import com.doumee.dao.business.web.request.GoodsorderCanBanlanceDTO;
import com.doumee.dao.business.web.response.GoodsorderDetailDTO;
import com.doumee.dao.business.web.response.GoodsorderDetailVO;
import com.doumee.service.business.GoodsorderService;
import com.doumee.service.business.GoodsorderService;
import com.doumee.service.business.RefundService;
@@ -22,6 +24,7 @@
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -103,8 +106,26 @@
    @RequiresPermissions("business:goodsorder:exportExcel")
    public void exportExcel (@RequestBody PageWrap<Goodsorder> pageWrap, HttpServletResponse response) {
        ExcelExporter.build(Goodsorder.class).exportWithFirstAndEnd(goodsorderService.findPage(pageWrap).getRecords(), "订单列表_"+System.currentTimeMillis(),"订单列表_"+ DateUtil.getPlusTime2(DateUtil.getCurrentDate()),null, response);
    }
    @ApiOperation("套餐卡订单列表-导出Excel")
    @PostMapping("/discountExportExcel")
    @RequiresPermissions("business:goodsorder:exportExcel")
    public void discountExportExcel (@RequestBody PageWrap<Goodsorder> pageWrap, HttpServletResponse response) {
        List<Goodsorder> goodsorders = goodsorderService.findPage(pageWrap).getRecords();
        List<DiscountGoodsorderExportVO> discountGoodsorderExportVOList = new ArrayList<>();
        for (Goodsorder goodsorder:goodsorders) {
            DiscountGoodsorderExportVO discountGoodsorderExportVO = new DiscountGoodsorderExportVO();
            BeanUtils.copyProperties(goodsorder,discountGoodsorderExportVO);
            discountGoodsorderExportVO.setPayMoney(discountGoodsorderExportVO.getPayMoney());
            discountGoodsorderExportVOList.add(discountGoodsorderExportVO);
        }
        ExcelExporter.build(DiscountGoodsorderExportVO.class).exportWithFirstAndEnd(discountGoodsorderExportVOList, "订单列表_"+System.currentTimeMillis(),"订单列表_"+ DateUtil.getPlusTime2(DateUtil.getCurrentDate()),null, response);
    }
    @ApiOperation("收款明细(微信对账明细)_分页查询")
    @PostMapping("/pageAccountDetail")
    @RequiresPermissions("business:goodsorder:query")
@@ -124,6 +145,12 @@
        return ApiResponse.success(goodsorderService.getGoodsorderDetailDTO(id));
    }
    @ApiOperation("订单详情-套餐卡")
    @GetMapping("/discountOrderDetail")
    @RequiresPermissions("business:goodsorder:query")
    public ApiResponse<GoodsorderDetailVO> discountOrderDetail(@RequestParam String id) {
        return ApiResponse.success(goodsorderService.getGoodsorderDetailForDisCount(id));
    }
    /**
     * å¼ºåˆ¶ç»“算订单
server/services/src/main/java/com/doumee/core/constants/Constants.java
@@ -118,7 +118,9 @@
        REFUND(2,"结算退押金", "结算退押金"),
        PLATFORMREFUND(3,"平台退押金", "平台退押金")
        PLATFORM_REFUND(3,"平台退款", "平台退款"),
        DISCOUNT(4,"平台套餐", "平台套餐")
        ;
        String name;
server/services/src/main/java/com/doumee/core/utils/DateUtil.java
@@ -68,6 +68,15 @@
        return dt1;
    }
    public static String getXDaysAfter(Date date, Integer days){
        Timestamp currentTimestamp = new Timestamp(date.getTime());
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(currentTimestamp);
        calendar.add(Calendar.DAY_OF_YEAR, days-1); // åœ¨å½“前时间基础上添加指定的天数
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        return formatter.format(calendar.getTime());
    }
    /**
     * @author Pablo Descrption:ȡ�õ�ǰ����getgetg get Date format
     *         Example:2008-05-15
@@ -2154,19 +2163,19 @@
    /**
     * å¾—出两个日期之间的间隔天数
     *
     * @param strFromDate
     * @param smallDate
     *            æ ¼å¼ä¸ºyyyy-MM-dd
     * @param strToDate
     * @param bigDate
     *            æ ¼å¼ä¸ºyyyy-MM-dd
     * @return int
     */
    public static long getBetweenDays(String strFromDate, String strToDate) {
    public static long getBetweenDays(String smallDate, String bigDate) {
        long m_intervalday = 0;// åˆå§‹åŒ–时间间隔的值为0
        // ä½¿ç”¨çš„æ—¶é—´æ ¼å¼ä¸ºyyyy-MM-dd
        SimpleDateFormat m_simpledateformat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date fromDate = m_simpledateformat.parse(strFromDate);
            Date toDate = m_simpledateformat.parse(strToDate);
            Date fromDate = m_simpledateformat.parse(smallDate);
            Date toDate = m_simpledateformat.parse(bigDate);
            m_intervalday = toDate.getTime() - fromDate.getTime();// è®¡ç®—所得为微秒数
            m_intervalday = m_intervalday / 1000 / 60 / 60 / 24;// è®¡ç®—所得的天数
@@ -2762,8 +2771,7 @@
    public static void main(String[] args) {
        try {
            System.out.println((int)(3.222));
            System.out.println(DateUtil.getBetweenDays("2025-02-13","2025-02-15"));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
server/services/src/main/java/com/doumee/dao/business/DiscountLogMapper.java
@@ -2,11 +2,12 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.doumee.dao.business.model.DiscountLog;
import com.github.yulichang.base.mapper.MPJJoinMapper;
/**
 * @author æ±Ÿè¹„蹄
 * @date 2025/02/17 09:43
 */
public interface DiscountLogMapper extends BaseMapper<DiscountLog> {
public interface DiscountLogMapper extends MPJJoinMapper<DiscountLog> {
}
server/services/src/main/java/com/doumee/dao/business/model/Discount.java
@@ -157,6 +157,12 @@
    private Integer bikeOrElec;
    @TableField(exist = false)
    @ApiModelProperty(value = "每日单价", example = "1")
    private BigDecimal dayPrice;
    @TableField(exist = false)
    @ApiModelProperty(value = "图片全路径", example = "1")
    private String fullImgUrl;
}
server/services/src/main/java/com/doumee/dao/business/model/DiscountLog.java
@@ -1,5 +1,6 @@
package com.doumee.dao.business.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.doumee.core.annotation.excel.ExcelColumn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -79,4 +80,8 @@
    @ExcelColumn(name="关联订单编码(关联goodsorder)")
    private String goodsorderId;
    @ApiModelProperty(value = "创建人名称")
    @TableField(exist = false)
    private String creatorName;
}
server/services/src/main/java/com/doumee/dao/business/model/DiscountMember.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
import java.math.BigDecimal;
import java.util.List;
/**
 * ç”¨æˆ·éª‘行套餐卡关联表
@@ -117,8 +118,8 @@
    @ApiModelProperty(value = "销量每天限额", example = "1")
    private Integer saleDayLimit;
    @ApiModelProperty(value = "状态 0正常 1作废 2过期或用完 ", example = "1")
    @ExcelColumn(name="套餐状态",index = 7,width = 10,valueMapping = "0=正常;1=作废;2=过期或用完")
    @ApiModelProperty(value = "状态 0正常 1作废 2待支付", example = "1")
    @ExcelColumn(name="套餐状态",index = 7,width = 10,valueMapping = "0=正常;1=作废;2=待支付")
    private Integer status;
    @ApiModelProperty(value = "用户编码(关联member)")
@@ -144,4 +145,13 @@
    @ExcelColumn(name="使用次数",index = 4,width = 20)
    @TableField(exist = false)
    private Integer useTimes;
    @ApiModelProperty(value = "骑行记录")
    @TableField(exist = false)
    private List<DiscountLog> discountLogList;
    @ApiModelProperty(value = "操作记录")
    @TableField(exist = false)
    private List<DiscountLog> optLogList;
}
server/services/src/main/java/com/doumee/dao/business/model/Goodsorder.java
@@ -136,6 +136,10 @@
    @TableField(exist = false)
    private Integer closeStatus;
    @ApiModelProperty(value = "操作人")
    @ExcelColumn(name="操作人",index = 9,width = 10,align = HorizontalAlignment.CENTER)
    @TableField(exist = false)
    private String refundUserName;
    @ApiModelProperty(value = "套餐名称", example = "2023-10-01 15:12:01")
    @TableField(exist = false)
server/services/src/main/java/com/doumee/dao/business/model/Transactions.java
@@ -63,7 +63,7 @@
    @ExcelColumn(name="交易金额")
    private BigDecimal money;
    @ApiModelProperty(value = "交易类型 0租车押金 1结算实际消费 2结算退款 3平台退款", example = "1")
    @ApiModelProperty(value = "交易类型 0租车押金 1结算实际消费 2结算退款 3平台退款 4购买套餐 ", example = "1")
    @ExcelColumn(name="交易类型 0租车押金 1结算实际消费 2结算退款 3平台退款")
    private Integer type;
@@ -91,8 +91,8 @@
    @ExcelColumn(name="用户当前余额(分)")
    private BigDecimal balance;
    @ApiModelProperty(value = "交易关联对象类型 0订单 1退款单", example = "1")
    @ExcelColumn(name="交易关联对象类型 0订单 1退款单")
    @ApiModelProperty(value = "交易关联对象类型 0订单 1退款单 2套餐购买", example = "1")
    @ExcelColumn(name="交易关联对象类型 0订单 1退款单 2套餐购买")
    private Integer objType;
    @ApiModelProperty(value = "交易关联对象编码")
server/services/src/main/java/com/doumee/dao/business/vo/DiscountGoodsorderExportVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package com.doumee.dao.business.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.doumee.core.annotation.excel.ExcelColumn;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import java.math.BigDecimal;
import java.util.Date;
/**
 * è®¢å•信息表
 * @author æ±Ÿè¹„蹄
 * @date 2023/09/27 18:06
 */
@Data
@ApiModel("套餐订单导出对象")
public class DiscountGoodsorderExportVO {
    @ApiModelProperty(value = "订单编码")
    @ExcelColumn(name="订单编号",width = 10,index = 1,align = HorizontalAlignment.CENTER)
    private String code;
    @ApiModelProperty(value = "套餐卡名称")
    @ExcelColumn(name="套餐卡",width = 10,index = 2,align = HorizontalAlignment.CENTER)
    private String discountName;
    @ApiModelProperty(value = "合计(元)")
    @ExcelColumn(name="合计(元)",width = 10,index = 3,align = HorizontalAlignment.CENTER)
    private BigDecimal money;
    @ApiModelProperty(value = "实付(元)")
    @ExcelColumn(name="实付(元)",width = 10,index = 4,align = HorizontalAlignment.CENTER)
    private BigDecimal payMoney;
    @ApiModelProperty(value = "已退金额(元)")
    @ExcelColumn(name="已退金额(元)",width = 10,index = 5,align = HorizontalAlignment.CENTER)
    private BigDecimal refundMoney;
    @ApiModelProperty(value = "用户openid" )
    @TableField(exist = false)
    @ExcelColumn(name="用户信息",index = 6 ,width = 10,align = HorizontalAlignment.CENTER)
    private String openid;
    @ApiModelProperty(value = "支付方式 0微信 1支付宝")
    @ExcelColumn(name="支付方式",index = 7 ,width = 10,valueMapping = "0=微信;1=支付宝;",align = HorizontalAlignment.CENTER)
    private Integer payWay;
    @ApiModelProperty(value = "订单来源")
    @ExcelColumn(name="订单来源",index = 8 ,width = 10,valueMapping = "0=小程序端;",align = HorizontalAlignment.CENTER)
    private Integer origin;
    @ApiModelProperty(value = "操作人")
    @ExcelColumn(name="操作人",index = 9,width = 10,align = HorizontalAlignment.CENTER)
    private String refundUserName;
    @ApiModelProperty(value = "创建时间")
    @ExcelColumn(name="创建时间",index = 10,width = 16,dateFormat = "yyyy-MM-dd HH:mm:ss",align = HorizontalAlignment.CENTER)
    private Date createDate;
}
server/services/src/main/java/com/doumee/dao/business/web/request/DiscountMemberDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package com.doumee.dao.business.web.request;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.doumee.core.annotation.excel.ExcelColumn;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * ç”¨æˆ·éª‘行套餐卡关联表
 * @author æ±Ÿè¹„蹄
 * @date 2025/02/17 09:43
 */
@Data
public class DiscountMemberDTO {
    @ApiModelProperty(value = "编码")
    private String id;
    @ApiModelProperty(value = "备注")
    private String info;
    @ApiModelProperty(value = "票号")
    private String code;
    @ApiModelProperty(value = "名称")
    private String name;
    @ApiModelProperty(value = "用户opneid")
    private String openid;
    @ApiModelProperty(value = "状态 0正常 1作废 2过期或用完", example = "1")
    private Integer status;
    @ApiModelProperty(value = "增加天数")
    private Integer addDays;
    @ApiModelProperty(value = "选中数据")
    private List<String> choseIdList;
}
server/services/src/main/java/com/doumee/dao/business/web/response/GoodsorderDetailVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.doumee.dao.business.web.response;
import com.doumee.dao.business.model.DiscountMember;
import com.doumee.dao.business.model.Goodsorder;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.Refund;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * @author T14
 */
@Data
@ApiModel("订单详情")
public class GoodsorderDetailVO {
    @ApiModelProperty(value = "订单信息")
    private Goodsorder goodsOrder;
    @ApiModelProperty(value = "买家信息")
    private Member member;
    @ApiModelProperty(value = "套餐信息")
    private DiscountMember discountMember;
    @ApiModelProperty(value = "退款记录")
    private List<Refund> refundList;
}
server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java
@@ -1,6 +1,7 @@
package com.doumee.dao.business.web.response;
import com.doumee.dao.business.model.Ad;
import com.doumee.dao.business.model.Discount;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -37,7 +38,6 @@
    @ApiModelProperty(value = "租赁须知")
    private String leaseNotice;
    @ApiModelProperty(value = "押金状态:0=未支付押金;1=已支付押金; 2=退款中")
    private Integer depositStatus = 0;
@@ -65,5 +65,7 @@
    @ApiModelProperty(value = "广告信息")
    private List<Ad> adList;
    @ApiModelProperty(value = "热销套餐")
    private List<Discount> discountList;
}
server/services/src/main/java/com/doumee/service/business/DiscountMemberService.java
@@ -3,6 +3,8 @@
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.model.DiscountMember;
import com.doumee.dao.business.web.request.DiscountMemberDTO;
import java.util.List;
/**
@@ -63,6 +65,7 @@
     */
    DiscountMember findById(String id);
    DiscountMember getDetail(String id);
    /**
     * æ¡ä»¶æŸ¥è¯¢å•条记录
     *
@@ -94,4 +97,8 @@
     * @return long
     */
    long count(DiscountMember discountMember);
    void cancel(DiscountMemberDTO model);
    void adjust(DiscountMemberDTO model);
}
server/services/src/main/java/com/doumee/service/business/DiscountService.java
@@ -87,6 +87,7 @@
     */
    PageData<Discount> findPage(PageWrap<Discount> pageWrap);
    void dealPrice(Discount discount);
    /**
     * æ¡ä»¶ç»Ÿè®¡
     *
server/services/src/main/java/com/doumee/service/business/GoodsorderService.java
@@ -12,6 +12,7 @@
import com.doumee.dao.business.web.request.BackElecBikeRequest;
import com.doumee.dao.business.web.request.GoodsorderBackDTO;
import com.doumee.dao.business.web.request.GoodsorderCanBanlanceDTO;
import com.doumee.dao.business.web.response.GoodsorderDetailVO;
import com.doumee.dao.business.web.response.HomeResponse;
import com.doumee.dao.business.web.response.GoodsorderDetailDTO;
import com.doumee.dao.business.web.response.RidesDetailResponse;
@@ -127,9 +128,16 @@
     */
    Object createGoodsOrderPay(String memberId);
    /**
     * å¥—餐购买
     * @param discountId
     * @param memberId
     * @return
     */
    Object createDiscountOrderPay(String discountId,String memberId);
    /**
     * æŠ¼é‡‘支付回调业务
     * æ”¯ä»˜å›žè°ƒä¸šåŠ¡
     * @param preOrderId
     * @return
     */
@@ -143,7 +151,12 @@
     */
    GoodsorderDetailDTO getGoodsorderDetailDTO(String id);
    /**
     * å¥—餐卡订单详情
     * @param id
     * @return
     */
    GoodsorderDetailVO getGoodsorderDetailForDisCount(String id);
    /**
     * å¼ºåˆ¶ç»“算订单
     * @param id
server/services/src/main/java/com/doumee/service/business/impl/DiscountMemberServiceImpl.java
@@ -1,13 +1,22 @@
package com.doumee.service.business.impl;
import com.doumee.core.constants.Constants;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.DiscountLogMapper;
import com.doumee.dao.business.DiscountMemberMapper;
import com.doumee.dao.business.join.DiscountMemberJoinMapper;
import com.doumee.dao.business.model.Discount;
import com.doumee.dao.business.model.DiscountLog;
import com.doumee.dao.business.model.DiscountMember;
import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.web.request.DiscountMemberDTO;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.DiscountMemberService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -15,13 +24,16 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * ç”¨æˆ·éª‘行套餐卡关联表Service实现
@@ -36,6 +48,10 @@
    @Autowired
    private DiscountMemberJoinMapper discountMemberJoinMapper;
    @Autowired
    private DiscountLogMapper discountLogMapper;
    @Override
    public String create(DiscountMember discountMember) {
@@ -83,6 +99,27 @@
    }
    @Override
    public DiscountMember getDetail(String id) {
        DiscountMember discountMember = discountMemberMapper.selectById(id);
        if(Objects.isNull(discountMember)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        List<DiscountLog> discountLogList = discountLogMapper.selectJoinList(DiscountLog.class,new MPJLambdaWrapper<DiscountLog>()
                .selectAll(DiscountLog.class)
                .selectAs(SystemUser::getRealname,DiscountLog::getCreatorName)
                .eq(DiscountLog::getDiscountMemberId,discountMember.getId())
                .eq(DiscountLog::getIsdeleted,Constants.ZERO)
                .orderByDesc(DiscountLog::getId)
        );
        if(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(discountLogList)){
            discountMember.setDiscountLogList(discountLogList.stream().filter(i->Constants.equalsInteger(i.getType(),Constants.ZERO)).collect(Collectors.toList()));
            discountMember.setOptLogList(discountLogList.stream().filter(i->!Constants.equalsInteger(i.getType(),Constants.ZERO)).collect(Collectors.toList()));
            discountMember.setUseTimes(discountMember.getDiscountLogList().size());
        }
        return discountMember;
    }
    @Override
    public DiscountMember findOne(DiscountMember discountMember) {
        QueryWrapper<DiscountMember> wrapper = new QueryWrapper<>(discountMember);
        return discountMemberMapper.selectOne(wrapper);
@@ -110,6 +147,95 @@
        return PageData.from(discountMemberJoinMapper.selectJoinPage(page, DiscountMember.class,queryWrapper));
    }
    @Override
    public void cancel(DiscountMemberDTO model){
        LoginUserInfo principal = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        MPJLambdaWrapper<DiscountMember> queryWrapper = new MPJLambdaWrapper<>();
        queryWrapper.selectAll(DiscountMember.class)
                .leftJoin(Member.class,Member::getId,DiscountMember::getMemberId)
                .eq(DiscountMember::getStatus,Constants.ZERO)
                .like(StringUtils.isNotBlank(model.getCode()),DiscountMember::getCode,model.getCode())
                .like(StringUtils.isNotBlank(model.getName()),DiscountMember::getName,model.getName())
                .like(StringUtils.isNotBlank(model.getOpenid()),Member::getOpenid,model.getOpenid())
                .eq(Objects.nonNull(model.getId()),DiscountMember::getId,model.getId())
                .in(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(model.getChoseIdList())
                        ,DiscountMember::getId,model.getChoseIdList())
                .orderByDesc(DiscountMember::getId);
        List<DiscountMember> list = discountMemberJoinMapper.selectJoinList(DiscountMember.class,queryWrapper);
        if(CollectionUtils.isEmpty(list)){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"未查询到可报废的套餐卡信息");
        }
        for (DiscountMember discountMember:list) {
            discountMemberMapper.update(null,new UpdateWrapper<DiscountMember>().lambda()
                    .set(DiscountMember::getStatus,Constants.ONE)
                    .eq(DiscountMember::getId,discountMember.getId())
            );
            //操作日志
            DiscountLog discountLog = new DiscountLog();
            discountLog.setIsdeleted(Constants.ZERO);
            discountLog.setCreator(principal.getId());
            discountLog.setCreateDate(new Date());
            discountLog.setDiscountMemberId(discountMember.getId());
            discountLog.setType(Constants.ONE);
            discountLog.setInfo(model.getInfo());
            discountLog.setEditInfo("退货退卡");
            discountLog.setGoodsorderId(discountMember.getGoodsorderId());
            discountLogMapper.insert(discountLog);
        }
        //退款操作
    }
    @Override
    public void adjust(DiscountMemberDTO model){
        if(Objects.isNull(model)
        || Objects.isNull(model.getAddDays())){
            throw new BusinessException(ResponseStatus.BAD_REQUEST);
        }
        LoginUserInfo principal = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
        MPJLambdaWrapper<DiscountMember> queryWrapper = new MPJLambdaWrapper<>();
        queryWrapper.selectAll(DiscountMember.class)
                .leftJoin(Member.class,Member::getId,DiscountMember::getMemberId)
                .eq(DiscountMember::getStatus,Constants.ZERO)
                .like(StringUtils.isNotBlank(model.getCode()),DiscountMember::getCode,model.getCode())
                .like(StringUtils.isNotBlank(model.getName()),DiscountMember::getName,model.getName())
                .like(StringUtils.isNotBlank(model.getOpenid()),Member::getOpenid,model.getOpenid())
                .eq(Objects.nonNull(model.getId()),DiscountMember::getId,model.getId())
                .in(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(model.getChoseIdList())
                        ,DiscountMember::getId,model.getChoseIdList())
                .orderByDesc(DiscountMember::getId);
        List<DiscountMember> list = discountMemberJoinMapper.selectJoinList(DiscountMember.class,queryWrapper);
        if(CollectionUtils.isEmpty(list)){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"未查询到可调整的套餐卡信息");
        }
        for (DiscountMember discountMember:list) {
            if(!Constants.equalsInteger(discountMember.getStatus(),Constants.ZERO)){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"【"+discountMember.getCode()+"】套餐卡已作废,无法进行该操作");
            }
            String useEndDate = DateUtil.getXDaysAfter(discountMember.getUseEndDate(),model.getAddDays());
            discountMemberMapper.update(null,new UpdateWrapper<DiscountMember>().lambda()
                            //.setSql(" use_end_date =  use_end_date::date +   " + model.getAddDays() )
                            .set(DiscountMember::getUseEndDate,useEndDate)
                    .eq(DiscountMember::getId,discountMember.getId())
            );
            //操作日志
            DiscountLog discountLog = new DiscountLog();
            discountLog.setIsdeleted(Constants.ZERO);
            discountLog.setCreator(principal.getId());
            discountLog.setCreateDate(new Date());
            discountLog.setDiscountMemberId(discountMember.getId());
            discountLog.setType(Constants.TWO);
            discountLog.setGoodsorderId(discountMember.getGoodsorderId());
            discountLog.setEditInfo(model.getInfo());
            discountLog.setEditDays(model.getAddDays());
            discountLogMapper.insert(discountLog);
        }
    }
    @Override
    public long count(DiscountMember discountMember) {
        QueryWrapper<DiscountMember> wrapper = new QueryWrapper<>(discountMember);
server/services/src/main/java/com/doumee/service/business/impl/DiscountServiceImpl.java
@@ -7,6 +7,7 @@
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.DiscountMapper;
import com.doumee.dao.business.join.BikeRepairJoinMapper;
@@ -29,6 +30,7 @@
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@@ -58,6 +60,9 @@
        discount.setIsdeleted(Constants.ZERO);
        discount.setCreateDate(new Date());
        discount.setCreator(userInfo.getId());
        if(Constants.equalsInteger(discount.getUseType(),Constants.ZERO)){
            discount.setUseDays((int) (DateUtil.getBetweenDays(DateUtil.dateToString(discount.getUseStartDate(),"yyyy-MM-dd"),DateUtil.dateToString(discount.getUseEndDate(),"yyyy-MM-dd"))));
        }
        discountMapper.insert(discount);
        return discount.getId();
    }
@@ -68,15 +73,15 @@
                || StringUtils.isBlank(discount.getName())
                || Objects.isNull(discount.getType())
                || Objects.isNull(discount.getLimitType())
                || (discount.getLimitTime()==Constants.ONE && Objects.isNull(discount.getLimitTime()))
                || Objects.isNull(discount.getPrice())
                || (Constants.equalsInteger(discount.getLimitTime(),Constants.ONE) && Objects.isNull(discount.getLimitTime()))
                || Objects.isNull(discount.getPrice()) || discount.getPrice().compareTo(BigDecimal.ZERO) <= Constants.ZERO
                || Objects.isNull(discount.getChannel())
                || Objects.isNull(discount.getStartDate())
                || Objects.isNull(discount.getEndDate())
                || Objects.isNull(discount.getUseType())
                || (discount.getUseType()==Constants.ZERO && ( Objects.isNull(discount.getUseStartDate()) || Objects.isNull(discount.getUseEndDate()) ))
                || (discount.getUseType()==Constants.ONE && Objects.isNull(discount.getUseDays()))
                || (discount.getUseType()==Constants.TWO && ( Objects.isNull(discount.getUseStartDate()) || Objects.isNull(discount.getUseDays()) ))
                || (Constants.equalsInteger(discount.getUseType(),Constants.ZERO) && ( Objects.isNull(discount.getUseStartDate()) || Objects.isNull(discount.getUseEndDate()) ))
                || (Constants.equalsInteger(discount.getUseType(),Constants.ONE) && Objects.isNull(discount.getUseDays()))
                || (Constants.equalsInteger(discount.getUseType(),Constants.TWO) && ( Objects.isNull(discount.getUseStartDate()) || Objects.isNull(discount.getUseDays()) ))
                || (Objects.isNull(discount.getUseHoliday()) || Objects.isNull(discount.getUseWorkday()) )
                || (Objects.isNull(discount.getIsbike()) || Objects.isNull(discount.getIselecbike()) )
        ){
@@ -86,15 +91,16 @@
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"销售时段信息错误");
        }
        //固定日期生效
        if(discount.getUseType()==Constants.ZERO &&
        if(Constants.equalsInteger(discount.getUseType(),Constants.ZERO) &&
                ( discount.getUseStartDate().getTime()>discount.getUseEndDate().getTime() ||
                        discount.getUseStartDate().getTime() < discount.getStartDate().getTime() ||
                        discount.getUseEndDate().getTime() > discount.getEndDate().getTime()
                ) ){
            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"使用时段信息错误");
        }
        //指定日期生效
        if(discount.getUseType()==Constants.TWO &&
        if(Constants.equalsInteger(discount.getUseType(),Constants.TWO) &&
                ( discount.getUseStartDate().getTime()<discount.getStartDate().getTime() ||
                        discount.getUseStartDate().getTime() > discount.getEndDate().getTime()
                ) ){
@@ -125,8 +131,6 @@
        }
        discountMapper.update(null,new UpdateWrapper<Discount>().lambda().set(Discount::getStatus,discount.getStatus()).eq(Discount::getId,discount.getId()));
    }
    @Override
@@ -168,6 +172,7 @@
        if(Objects.isNull(discount)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        this.dealPrice(discount);
        if(StringUtils.isNotBlank(discount.getImgurl())){
            String path =systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.FILE_DIR).getCode()+
                    systemDictDataBiz.queryByCode(Constants.SYSTEM, Constants.DISCOUNT).getCode();
@@ -203,10 +208,31 @@
                .eq(Objects.nonNull(modele.getBikeOrElec()) && Constants.equalsInteger(modele.getBikeOrElec(),Constants.TWO),Discount::getIselecbike, Constants.ONE)
                .eq(Discount::getIsdeleted, Constants.ZERO)
                .eq( pageWrap.getModel().getStatus() !=null,BikeRepair::getStatus,pageWrap.getModel().getStatus());
        queryWrapper.orderByDesc(Discount::getCreateDate);
        return PageData.from(discountJoinMapper.selectJoinPage(page, Discount.class,queryWrapper));
                queryWrapper.orderByDesc(Discount::getCreateDate);
        PageData<Discount> pageData = PageData.from(discountJoinMapper.selectJoinPage(page, Discount.class,queryWrapper));
        for (Discount discount:pageData.getRecords()) {
            dealPrice(discount);
        }
        return pageData;
    }
    @Override
    public void dealPrice(Discount discount){
        if(Objects.isNull(discount)
            || Objects.isNull(discount.getUseEndDate())
            || Objects.isNull(discount.getUseStartDate())
            || Objects.isNull(discount.getPrice())
            || discount.getPrice().compareTo(BigDecimal.ZERO)==0
        ){
            discount.setDayPrice(BigDecimal.ZERO);
            return;
        }
        discount.setDayPrice(discount.getPrice().divide(new BigDecimal(Long.toString(discount.getUseDays())),2));
    }
    @Override
    public long count(Discount discount) {
        QueryWrapper<Discount> wrapper = new QueryWrapper<>(discount);
server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java
@@ -34,6 +34,7 @@
import com.doumee.dao.business.web.response.*;
import com.doumee.dao.business.join.MemberRidesJoinMapper;
import com.doumee.dao.business.model.*;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.GoodsorderService;
import com.doumee.service.business.MemberRidesService;
import com.doumee.service.business.PricingRuleService;
@@ -48,6 +49,7 @@
import org.apache.shiro.SecurityUtils;
import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -81,6 +83,8 @@
    @Autowired
    private DiscountLogMapper discountLogMapper;
    @Autowired
    private DiscountMapper discountMapper;
    @Autowired
    GoodsorderJoinMapper goodsorderJoinMapper;
@@ -200,13 +204,6 @@
            if (Objects.nonNull(model.getEndDate())){
                queryWrapper.le( Goodsorder::getPayDate, Utils.Date.getEnd(model.getEndDate()));
            }
//            queryWrapper.eq(model.getPayStatus() !=null,Goodsorder::getPayStatus,model.getPayStatus());
//            queryWrapper.like(model.getCode() !=null,Goodsorder::getCode,model.getCode());
//            queryWrapper.like(model.getId() !=null,Goodsorder::getId,model.getId());
//            queryWrapper.eq(model.getStatus() !=null,Goodsorder::getStatus,model.getStatus());
//            queryWrapper.like(model.getOnlineOrderid() !=null,Goodsorder::getOnlineOrderid,model.getOnlineOrderid());
//            queryWrapper.like(model.getOpenid() !=null,Member::getOpenid,model.getOpenid());
            queryWrapper.eq(Goodsorder::getIsdeleted,Constants.ZERO);
            queryWrapper.eq(Goodsorder::getPayStatus,Constants.ONE);
            queryWrapper.orderByAsc(Goodsorder::getPayDate);
@@ -328,6 +325,7 @@
        queryWrapper.eq(Objects.nonNull(pageWrap.getModel().getType()),Goodsorder::getType,pageWrap.getModel().getType());
        queryWrapper.select("(select  sum(er.discount_price) from member_rides  er  where er.ordre_id=t.id and er.isdeleted=0) as discountMoney," +
                "(select  er.status from member_rides  er  where er.ordre_id=t.id order by er.create_date desc limit 1) as memberRidesStatus");
        queryWrapper.select("( select s.realname from refund r left join system_user s on r.creator = s.id where r.obj_id = t.id order by r.id desc  limit 1  ) ",Goodsorder::getRefundUserName);
        if(Objects.nonNull(pageWrap.getModel().getCloseStatus()) && pageWrap.getModel().getCloseStatus().equals(Constants.ZERO)){
            queryWrapper.ne(Goodsorder::getStatus,Constants.GOODSORDER_STATUS.CLOSE.getKey());
        }
@@ -380,6 +378,19 @@
                .eq("status",Constants.ZERO)
                .orderByAsc("sortnum")
        ));;
        homeResponse.setDiscountList(
                discountMapper.selectList(new QueryWrapper<Discount>().lambda()
                        .eq(Discount::getIsdeleted,Constants.ZERO)
                        .eq(Discount::getStatus,Constants.ZERO)
                        .orderByDesc(Discount::getId)
                        .last(" limit 3 ")
                )
        );
        if(com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(homeResponse.getDiscountList())){
            for (Discount discount:homeResponse.getDiscountList()) {
                discount.setDayPrice(discount.getPrice().divide(new BigDecimal(Integer.toString(discount.getUseDays())),2));
            }
        }
        if(!Objects.isNull(homeResponse.getAdList())&&homeResponse.getAdList().size()>Constants.ZERO){
            homeResponse.getAdList().forEach(i->{
                i.setImgfullurl(fullPath + i.getImgurl());
@@ -446,7 +457,6 @@
    @Override
    public GoodsorderDetailDTO getGoodsorderDetailDTO(String id) {
        List<PayOrderDTO> payOrderDTOList = new ArrayList<>();
        Goodsorder goodsorder = goodsorderMapper.selectById(id);
        if(goodsorder == null){
@@ -495,6 +505,45 @@
        goodsorderDetailDTO.setModel(goodsorder);//订单对象
        return goodsorderDetailDTO;
    }
    @Override
    public GoodsorderDetailVO getGoodsorderDetailForDisCount(String id){
        GoodsorderDetailVO goodsorderDetailVO = new GoodsorderDetailVO();
        Goodsorder goodsorder = goodsorderMapper.selectById(id);
        if(goodsorder == null){
            throw  new BusinessException(ResponseStatus.DATA_EMPTY );
        }
        if(Constants.equalsInteger(goodsorder.getType(),Constants.ZERO)){
            throw  new BusinessException(ResponseStatus.NOT_ALLOWED);
        }
        goodsorderDetailVO.setGoodsOrder(goodsorder);
        Member member = memberMapper.selectById(goodsorder.getMemberId());
        if(Objects.nonNull(member)){
            goodsorderDetailVO.setMember(member);
        }
        //查询商品信息
        DiscountMember discountMember = discountMemberMapper.selectById(goodsorder.getObjId());
        if(Objects.nonNull(discountMember)){
            goodsorderDetailVO.setDiscountMember(discountMember);
        }
        //查询所有有退款记录
        List<Refund> refunds = refundJoinMapper.selectJoinList(Refund.class,
                new MPJLambdaWrapper<Refund>()
                        .selectAll(Refund.class)
                        .selectAs(SystemUser::getRealname,Refund::getCreatorName)
                        .leftJoin(SystemUser.class,SystemUser::getId,Refund::getCreator)
                        .eq(Refund::getObjId,goodsorder.getId())
                        .orderByDesc(Refund::getId)
        );
        goodsorderDetailVO.setRefundList(refunds);
        return goodsorderDetailVO;
    }
    @Override
    public void forceCloseGoodsorder(String orderId) {
        //查询订单 ä¿¡æ¯
@@ -1071,6 +1120,7 @@
        goodsorder.setCreateDate(new Date());
        goodsorder.setIsdeleted(Constants.ZERO);
        goodsorder.setCode(goodsorder.getId());
        goodsorder.setType(Constants.ZERO);
        goodsorder.setMemberId(memberId);
        goodsorder.setMoney(new BigDecimal(systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.RENT_DEPOSIT).getCode()).multiply(new BigDecimal(100)));
        goodsorder.setStatus(Constants.goodsorderStatus.waitPay);
@@ -1085,16 +1135,101 @@
        }  catch (Exception e) {
            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),"对不起,发起支付失败~");
        }
        return response;
    }
    private Object getWxPayResponse(Goodsorder goodsorder,String openid) throws Exception {
    @Override
    public Object createDiscountOrderPay(String memberId,String discountId){
        Member member = memberMapper.selectById(memberId);
        if(Objects.isNull(member)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(),"未查询到用户对象信息");
        }
        Discount discount = discountMapper.selectById(discountId);
        this.checkDiscountSaleDate(discount);
        String discountMemberId = Constants.getUUID();
        String goodsorderId = Constants.getUUID();
        DiscountMember discountMember = new DiscountMember();
        BeanUtils.copyProperties(discount,discountMember);
        discountMember.setCreator(null);
        discountMember.setId(discountMemberId);
        discountMember.setCreateDate(new Date());
        discountMember.setEditDate(new Date());
        discountMember.setEditor(null);
        discountMember.setMemberId(memberId);
        discountMember.setStatus(Constants.TWO);
        discountMember.setGoodsorderId(goodsorderId);
        discountMemberMapper.insert(discountMember);
        Goodsorder goodsorder = new Goodsorder();
        goodsorder.setId(goodsorderId);
        goodsorder.setCreateDate(new Date());
        goodsorder.setIsdeleted(Constants.ZERO);
        goodsorder.setCode(goodsorder.getId());
        goodsorder.setMemberId(memberId);
        goodsorder.setType(Constants.ONE);
        goodsorder.setMoney(discount.getPrice().multiply(new BigDecimal(100)));
        goodsorder.setStatus(Constants.goodsorderStatus.waitPay);
        goodsorder.setPreOrderid(goodsorder.getId());
        goodsorder.setPayStatus(Constants.goodsorderPayStatus.waitPay);
        goodsorder.setPayWay(Constants.ZERO);
        goodsorder.setObjType(Constants.ZERO);
        goodsorder.setObjId(discount.getId());
        this.goodsorderMapper.insert(goodsorder);
        //调起支付
            Object response = null;
        Object response = null;
        try {
            response = getWxPayResponse(goodsorder,member.getOpenid());
        }  catch (Exception e) {
            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),"对不起,发起支付失败~");
        }
        return response;
    }
    public void checkDiscountSaleDate(Discount discount){
        if(Objects.isNull(discount)){
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(),"未查询到套餐信息");
        }
        if(!(Constants.equalsInteger(discount.getIsdeleted(),Constants.ZERO)
            ||Constants.equalsInteger(discount.getStatus(),Constants.ZERO))){
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(),"套餐已下架,无法进行购买,请刷新查看");
        }
        if(!(discount.getStartDate().getTime() < System.currentTimeMillis()
         && Utils.Date.getEnd(discount.getEndDate()).getTime() > System.currentTimeMillis() )){
            throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(),"套餐开放时间未到,无法进行购买");
        }
        //验证是否超过今日可售卖数量
        if(Objects.nonNull(discount.getSaleDayLimit())){
            if(goodsorderMapper.selectCount(new QueryWrapper<Goodsorder>().lambda()
                    .eq(Goodsorder::getType,Constants.ONE)
                    .eq(Goodsorder::getObjId,discount.getId())
                    .in(Goodsorder::getStatus,Constants.ZERO,Constants.ONE)
                    .apply(" EXTRACT(DAY FROM create_date) = EXTRACT(DAY FROM CURRENT_DATE) ")
            )>=discount.getSaleDayLimit()){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"套餐今日发行量已售罄,请明日查看!");
            }
        }
        if(Objects.nonNull(discount.getSaleLimit())){
            if(goodsorderMapper.selectCount(new QueryWrapper<Goodsorder>().lambda()
                    .eq(Goodsorder::getType,Constants.ONE)
                    .eq(Goodsorder::getObjId,discount.getId())
                    .in(Goodsorder::getStatus,Constants.ZERO,Constants.ONE)
                    .apply(" EXTRACT(YEAR FROM create_date) = EXTRACT(YEAR FROM CURRENT_DATE) ")
            )>=discount.getSaleDayLimit()){
                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"套餐发行量已售罄!");
            }
        }
    }
    private Object getWxPayResponse(Goodsorder goodsorder,String openid){
        //调起支付
        Object response = null;
        PrepayRequest request = new PrepayRequest();
        request.setAttach("createGoodsOrder");
        request.setDescription("森林公园自行车骑行押金支付");
        request.setDescription(Constants.equalsInteger(goodsorder.getType(),Constants.ZERO)?"森林公园自行车骑行押金支付":"森林公园自行车购买骑行套餐支付");
        request.setSpMchid(WxMiniConfig.wxProperties.getMchId());
        request.setSpAppid(WxMiniConfig.wxProperties.getAppId());
        request.setSubMchid(WxMiniConfig.wxProperties.getSubMchId());
@@ -1111,10 +1246,10 @@
            // è·Ÿä¹‹å‰ä¸‹å•示例一样,填充预下单参数
        PrepayWithRequestPaymentResponse resParam =  WxMiniConfig.jsapiExtService.prepayWithRequestPayment(request,WxMiniConfig.wxProperties.getSubAppId());
        response =resParam;
        return response;
    }
    /**
@@ -1240,6 +1375,50 @@
            return  ("处理成功!");
        }
        goodsorder.setOnlineOrderid(paymentNo);
        if(Constants.equalsInteger(goodsorder.getType(),Constants.ZERO)){
            this.dealGoodsOrderCallBack(goodsorder);
        }else{
            this.dealDiscountOrderCallBack(goodsorder);
        }
        return  ("处理成功!");
    }
    public void dealDiscountOrderCallBack(Goodsorder goodsorder){
        goodsorder.setPayStatus(Constants.goodsorderPayStatus.pay);
        goodsorder.setStatus(Constants.goodsorderStatus.pay);
        goodsorder.setPayDate(new Date());
        goodsorder.setEditDate(new Date());
        goodsorderMapper.updateById(goodsorder);
        DiscountMember discountMember = discountMemberMapper.selectOne(new QueryWrapper<DiscountMember>().lambda()
                .eq(DiscountMember::getGoodsorderId,goodsorder.getId()).last(" limit 1 "));
        discountMemberMapper.update(null,new UpdateWrapper<DiscountMember>().lambda()
                .set(DiscountMember::getStatus,Constants.ZERO)
                .eq(DiscountMember::getId,discountMember.getId())
        );
        Constants.TRANSACTIONS_TYPE discountConstants = Constants.TRANSACTIONS_TYPE.DISCOUNT;
        //存储交易流水表
        Transactions transactions = new Transactions();
        transactions.setMemberId(goodsorder.getMemberId());
        transactions.setCreateDate(new Date());
        transactions.setIsdeleted(Constants.ZERO);
        transactions.setOrderId(goodsorder.getId());
        transactions.setMoney(goodsorder.getMoney());
        transactions.setType(discountConstants.getKey());
        transactions.setPreOrderid(goodsorder.getPreOrderid());
        transactions.setOnlineOrderid(goodsorder.getOnlineOrderid());
        transactions.setDoneDate(new Date());
        transactions.setTitle(discountConstants.getName());
        transactions.setContent(discountConstants.getInfo());
        //transactions.setContent(discountMember.getName() +" æœ‰æ•ˆæœŸ" + DateUtil.dateToString(discountMember.getUseStartDate(),"yyyy-MM-dd") +"-"+DateUtil.dateToString(discountMember.getUseEndDate(),"yyyy-MM-dd"));
        transactions.setBalance(goodsorder.getMoney());
        transactions.setObjId(discountMember.getId());
        transactions.setObjType(Constants.TWO);
        transactionsMapper.insert(transactions);
    }
    public void dealGoodsOrderCallBack(Goodsorder goodsorder){
        goodsorder.setPayStatus(Constants.goodsorderPayStatus.pay);
        goodsorder.setStatus(Constants.goodsorderStatus.pay);
        goodsorder.setPayDate(new Date());
@@ -1262,9 +1441,7 @@
        transactions.setObjId(goodsorder.getId());
        transactions.setObjType(Constants.ZERO);
        transactionsMapper.insert(transactions);
        return  ("处理成功!");
    }
    /**
server/services/src/main/java/com/doumee/service/business/impl/TransactionsServiceImpl.java
@@ -1,20 +1,27 @@
package com.doumee.service.business.impl;
import com.doumee.core.constants.Constants;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.DiscountMemberMapper;
import com.doumee.dao.business.RefundMapper;
import com.doumee.dao.business.TransactionsMapper;
import com.doumee.dao.business.model.DiscountMember;
import com.doumee.dao.business.model.Transactions;
import com.doumee.service.business.TransactionsService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
/**
 * äº¤æ˜“流水表Service实现
@@ -26,6 +33,13 @@
    @Autowired
    private TransactionsMapper transactionsMapper;
    @Autowired
    private DiscountMemberMapper discountMemberMapper;
    @Autowired
    private RefundMapper refundMapper;
    @Override
    public String create(Transactions transactions) {
@@ -172,7 +186,23 @@
        Utils.MP.blankToNull(pageWrap.getModel());
        queryWrapper.lambda().eq(Transactions::getMemberId, memberId);
        queryWrapper.orderByDesc("create_date");
        return PageData.from(transactionsMapper.selectPage(page, queryWrapper));
        PageData<Transactions> pageData = PageData.from(transactionsMapper.selectPage(page, queryWrapper));
        for (Transactions transactions:pageData.getRecords()) {
            if(Constants.equalsInteger(transactions.getType(),Constants.TRANSACTIONS_TYPE.DISCOUNT.getKey())){
                //购买套餐 å¤„理内容
                 DiscountMember discountMember = discountMemberMapper.selectById(transactions.getObjId());
                 if(Objects.nonNull(discountMember)){
                     transactions.setContent(discountMember.getName() +" æœ‰æ•ˆæœŸ" + DateUtil.dateToString(discountMember.getUseStartDate(),"yyyy-MM-dd") +"-"+DateUtil.dateToString(discountMember.getUseEndDate(),"yyyy-MM-dd"));
                 }
            }else if(Constants.equalsInteger(transactions.getType(),Constants.TRANSACTIONS_TYPE.PLATFORM_REFUND.getKey())){
                DiscountMember discountMember = discountMemberMapper.selectOne(
                        new QueryWrapper<DiscountMember>().lambda().eq(DiscountMember::getGoodsorderId, transactions.getOrderId()).last("limit 1"));
                if(Objects.nonNull(discountMember)){
                    transactions.setContent(discountMember.getName() +" æœ‰æ•ˆæœŸ" + DateUtil.dateToString(discountMember.getUseStartDate(),"yyyy-MM-dd") +"-"+DateUtil.dateToString(discountMember.getUseEndDate(),"yyyy-MM-dd"));
                }
            }
        }
        return pageData;
    }
server/web/src/main/java/com/doumee/api/web/BusinessApi.java
@@ -12,6 +12,7 @@
import com.doumee.dao.business.model.*;
import com.doumee.dao.business.web.request.BackElecBikeRequest;
import com.doumee.dao.business.web.request.RepairRequest;
import com.doumee.dao.business.web.response.HomeResponse;
import com.doumee.dao.business.web.response.MemberRidesDetailResponse;
import com.doumee.dao.business.web.response.RidesDetailResponse;
import com.doumee.dao.system.model.SystemDictData;
@@ -67,6 +68,9 @@
    @Autowired
    private SystemDictDataBiz systemDictDataBiz;
    @Autowired
    private DiscountService discountService;
    @PreventRepeat
    @LoginRequired
    @ApiOperation(value = "押金支付", notes = "小程序端")
@@ -77,6 +81,20 @@
    public ApiResponse<Object> createGoodsOrderPay() {
        return  ApiResponse.success("操作成功",goodsorderService.createGoodsOrderPay(getMemberId()));
    }
    @PreventRepeat
    @LoginRequired
    @ApiOperation(value = "套餐购买", notes = "小程序端")
    @GetMapping("/createDiscountOrderPay")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true),
            @ApiImplicitParam(paramType = "query", dataType = "String", name = "discountId", value = "套餐主键", required = true)
    })
    public ApiResponse<Object> createDiscountOrderPay(@RequestParam String discountId) {
        return  ApiResponse.success("操作成功",goodsorderService.createDiscountOrderPay(discountId,getMemberId()));
    }
    @LoginRequired
    @ApiOperation(value = "骑行明细", notes = "小程序端")
@@ -112,6 +130,24 @@
        return ApiResponse.success(transactionsService.findPageForMini(pageWrap,getMemberId()));
    }
    @LoginRequired
    @ApiOperation(value = "热销套餐", notes = "热销套餐")
    @GetMapping("/discountPage")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse<PageData<Discount>> discountPage (@RequestBody PageWrap<Discount> pageWrap) {
        Discount discount = new Discount();
        discount.setStatus(Constants.ZERO);
        pageWrap.setModel(discount);
        return ApiResponse.success(discountService.findPage(pageWrap));
    }
    @LoginRequired
    @ApiOperation("基础配置数据")
    @GetMapping("/baseParamList")
server/web/src/main/java/com/doumee/api/web/HomeApi.java
@@ -49,6 +49,7 @@
        HomeResponse homeResponse = goodsorderService.getHome(getMemberId());
        return  ApiResponse.success("查询成功",homeResponse);
    }
    @ApiOperation(value = "测试电池控制", notes = "测试电池控制")
    @PostMapping("/testControl")
    public ApiResponse<APIResult<T0201_0500>> testControl(@RequestBody  T8500 param) {