Jelajahi Sumber

积分相关代码提交

yjwang 1 hari lalu
induk
melakukan
4109ed9716

+ 88 - 0
src/api/his/integralAfterSales.js

@@ -0,0 +1,88 @@
+import request from '@/utils/request'
+
+// 查询积分订单售后单列表
+export function listIntegralAfterSales(query) {
+  return request({
+    url: '/his/integralAfterSales/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询积分订单售后单详情
+export function getIntegralAfterSales(id) {
+  return request({
+    url: '/his/integralAfterSales/' + id,
+    method: 'get'
+  })
+}
+
+// 根据订单编号查询售后单详情
+export function getIntegralAfterSalesByOrderCode(orderCode) {
+  return request({
+    url: '/his/integralAfterSales/byOrderCode/' + orderCode,
+    method: 'get'
+  })
+}
+
+// 平台审核(status: 0 → 1)
+export function auditIntegralAfterSales(data) {
+  return request({
+    url: '/his/integralAfterSales/audit',
+    method: 'put',
+    data: data
+  })
+}
+
+// 撤销售后单(salesStatus: 0 → 1)
+export function cancelIntegralAfterSales(data) {
+  return request({
+    url: '/his/integralAfterSales/cancel',
+    method: 'put',
+    data: data
+  })
+}
+
+// 仓库审核(status: 2 → 3)
+export function warehouseAuditIntegralAfterSales(data) {
+  return request({
+    url: '/his/integralAfterSales/warehouseAudit',
+    method: 'put',
+    data: data
+  })
+}
+
+// 财务审核退款(status: 1/3 → 4,触发资金+积分退款)
+export function refundIntegralAfterSales(data) {
+  return request({
+    url: '/his/integralAfterSales/refund',
+    method: 'put',
+    data: data
+  })
+}
+
+// 编辑用户寄回物流(status: 1 → 2)
+export function updateIntegralAfterSales(data) {
+  return request({
+    url: '/his/integralAfterSales',
+    method: 'put',
+    data: data
+  })
+}
+
+// 导出积分订单售后单
+export function exportIntegralAfterSales(query) {
+  return request({
+    url: '/his/integralAfterSales/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询用户寄回物流信息
+export function getAfterSalesExpress(id) {
+  return request({
+    url: '/his/integralAfterSales/getExpress/' + id,
+    method: 'get'
+  })
+}

+ 26 - 0
src/api/his/integralOrder.js

@@ -78,3 +78,29 @@ export function getOrderUserPhone(orderId) {
     method: 'get'
   })
 }
+
+// 取消订单(未支付订单取消,退积分)
+export function cancelOrder(orderCode) {
+  return request({
+    url: '/his/integralOrder/cancelOrder',
+    method: 'post',
+    data: { orderCode }
+  })
+}
+
+// 管理员强制退款(不校验是否发货,退资金+退积分)
+export function mandatoryRefunds(orderCode) {
+  return request({
+    url: '/his/integralOrder/mandatoryRefunds',
+    method: 'post',
+    data: { orderCode }
+  })
+}
+
+// 确认收货(status: 2 待收货 → 3 已完成)
+export function finishOrder(orderCode) {
+  return request({
+    url: '/his/integralOrder/finishOrder/' + orderCode,
+    method: 'get'
+  })
+}

+ 9 - 0
src/api/hisStore/integralGoods.js

@@ -56,3 +56,12 @@ export function exportIntegralGoods(query) {
     params: query
   })
 }
+
+// 获取选择积分商品列表
+export function getChooseIntegralGoodsList(query) {
+  return request({
+    url: '/his/integralGoods/getChooseIntegralGoodsList',
+    method: 'get',
+    params: query
+  })
+}

+ 18 - 0
src/api/hisStore/shippingTemplates.js

@@ -9,6 +9,15 @@ export function listShippingTemplates(query) {
   })
 }
 
+// 组件-查询运费模板列表(无权限限制,供前端组件选择使用)
+export function componentList(query) {
+  return request({
+    url: '/store/store/shippingTemplates/componentList',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询运费模板详细
 export function getShippingTemplates(shippingId) {
   return request({
@@ -57,3 +66,12 @@ export function getAllShippingTemplates() {
     method: 'get'
   })
 }
+
+// 根据ids查询模板列表
+export function getByIds(query) {
+  return request({
+    url: '/store/store/shippingTemplates/getByIds',
+    method: 'get',
+    params: query
+  })
+}

+ 1 - 1
src/views/components/his/integralGoodsDetails.vue

@@ -33,7 +33,7 @@
 </template>
 
 <script>
-import { listIntegralGoods, getIntegralGoods, delIntegralGoods, addIntegralGoods, updateIntegralGoods, exportIntegralGoods } from "@/api/his/integralGoods";
+import { listIntegralGoods, getIntegralGoods, delIntegralGoods, addIntegralGoods, updateIntegralGoods, exportIntegralGoods } from "@/api/hisStore/integralGoods";
   export default {
     name: "coupon",
     data() {

+ 152 - 23
src/views/components/his/integralOrderDetails.vue

@@ -5,19 +5,8 @@
       </div>
 <div class="contentx" v-if="item!=null">
         <div class="desct"> 积分订单信息</div>
-        <div class="order-status" v-if="item!=null && item.status != 5" >
-                  <el-steps  :active="item.status==4?0:item.status" align-center finish-status="success">
-                    <el-step title="待支付"></el-step>
-                    <el-step title="待发货"></el-step>
-                    <el-step title="待收货"></el-step>
-                    <el-step title="已完成"></el-step>
-                  </el-steps>
-        </div>
          <el-card shadow="never" style="margin-top: 15px">
            <div class="operate-container"  v-if="item!=null">
-             <span style="margin-left: 20px" class="color-danger">订单状态:
-                <el-tag prop="status" v-for="(ite, index) in statusOptions"    v-if="item.status==ite.dictValue">{{ite.dictLabel}}</el-tag>
-             </span>
              <div class="operate-button-container"  v-if="item.status==1" v-hasPermi="['his:integralOrder:sendGoods']">
                 <el-button size="mini" @click="sendVisible=true" >发货</el-button>
              </div>
@@ -62,29 +51,93 @@
               :data="prod"
               size="small"
               style="margin-top: 20px" >
-              <el-table-column label="商品图片"  align="center">
+              <el-table-column label="商品图片" width="120" align="center">
                 <template slot-scope="scope">
-                  <img :src="scope.row.imgUrl" style="height: 80px">
+                  <img v-if="scope.row.imgUrl" :src="scope.row.imgUrl" style="height: 70px" />
+                  <span v-else>-</span>
                 </template>
               </el-table-column>
-              <el-table-column label="商品名称"  align="center">
+              <el-table-column label="商品名称" align="center" prop="goodsName" show-overflow-tooltip>
                 <template slot-scope="scope">
-                  <p>{{scope.row.goodsName}}</p>
+                  <p>{{ scope.row.goodsName || '-' }}</p>
                 </template>
               </el-table-column>
-              <el-table-column label="积分" align="center">
+              <el-table-column label="商品编号" width="180" align="center" prop="barCode">
                 <template slot-scope="scope">
-                  <p>¥{{scope.row.integral}}</p>
+                  <p>{{ scope.row.barCode || '-' }}</p>
                 </template>
               </el-table-column>
-              <el-table-column label="金额" align="center">
-               <template slot-scope="scope">
-                 <p>¥{{scope.row.cash || 0}}</p>
-               </template>
+              <el-table-column label="数量" width="100" align="center" prop="num">
+                <template slot-scope="scope">
+                  <p>{{ scope.row.num || 1 }}</p>
+                </template>
+              </el-table-column>
+              <el-table-column label="原价" width="120" align="center" prop="otPrice">
+                <template slot-scope="scope">
+                  <p v-if="scope.row.otPrice != null">¥{{ scope.row.otPrice }}</p>
+                  <p v-else>-</p>
+                </template>
+              </el-table-column>
+              <el-table-column label="积分" width="120" align="center" prop="integral">
+                <template slot-scope="scope">
+                  <p>{{ scope.row.integral || 0 }}</p>
+                </template>
+              </el-table-column>
+              <el-table-column label="现金" width="120" align="center" prop="cash">
+                <template slot-scope="scope">
+                  <p v-if="scope.row.cash != null">¥{{ scope.row.cash }}</p>
+                  <p v-else>-</p>
+                </template>
               </el-table-column>
-
             </el-table>
        </div>
+
+      <!-- 当前订单积分明细 -->
+      <div class="contentx" v-if="item!=null">
+        <div class="desct">
+          积分明细
+          <span class="font-small" style="margin-left: 10px; color: #909399; font-weight: normal;">
+            (当前订单关联的积分变动记录)
+          </span>
+        </div>
+        <el-table
+          border
+          v-loading="logsLoading"
+          :data="integralLogs"
+          size="small"
+          style="margin-top: 10px">
+          <el-table-column label="类别" align="center" prop="logType">
+            <template slot-scope="scope">
+              <el-tag v-for="opt in logTypeOptions" :key="opt.dictValue"
+                      v-if="String(scope.row.logType) === String(opt.dictValue)" size="mini">
+                {{ opt.dictLabel }}
+              </el-tag>
+              <span v-if="!logTypeOptions.some(opt => String(opt.dictValue) === String(scope.row.logType))">
+                {{ scope.row.logType }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="积分变动" width="140" align="center" prop="integral">
+            <template slot-scope="scope">
+              <span :class="scope.row.integral >= 0 ? 'color-success' : 'color-danger'">
+                {{ scope.row.integral >= 0 ? '+' : '' }}{{ scope.row.integral }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="积分余额" width="140" align="center" prop="balance">
+            <template slot-scope="scope">{{ scope.row.balance != null ? scope.row.balance : '-' }}</template>
+          </el-table-column>
+          <el-table-column label="业务ID" align="center" prop="businessId" show-overflow-tooltip>
+            <template slot-scope="scope">{{ scope.row.businessId || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="时间" width="180" align="center" prop="createTime">
+            <template slot-scope="scope">{{ scope.row.createTime }}</template>
+          </el-table-column>
+        </el-table>
+        <div v-if="!logsLoading && integralLogs.length === 0" class="empty-tip">
+          暂无该订单的积分变动记录
+        </div>
+      </div>
       <el-dialog
            width="50%"
            title="发货"
@@ -158,6 +211,7 @@
 
 <script>
 import {getExpress, listIntegralOrder, sendgoods,getIntegralOrder, delIntegralOrder, addIntegralOrder, updateIntegralOrder, exportIntegralOrder,getOrderUserPhone } from "@/api/his/integralOrder";
+import { getIntegralLogsByOrderId } from "@/api/his/userIntegralLogs";
   export default {
     name: "integralOrder",
     data() {
@@ -180,6 +234,10 @@ import {getExpress, listIntegralOrder, sendgoods,getIntegralOrder, delIntegralOr
         item:null,
         express:null,
         traces:[],
+        // 当前订单积分明细
+        integralLogs: [],
+        logsLoading: false,
+        logTypeOptions: [],
         rules:{
       deliveryCode: [
         { required: true, message: '请输入快递公司编号', trigger: 'blur' }
@@ -204,8 +262,45 @@ import {getExpress, listIntegralOrder, sendgoods,getIntegralOrder, delIntegralOr
       this.getDicts("sys_integral_order_status").then(response => {
         this.statusOptions = response.data;
       });
+      // 加载积分日志类型字典(合并本地兜底,与列表页保持一致)
+      this.getDicts("sys_integral_log_type").then(response => {
+        this.logTypeOptions = this.mergeLogTypeOptions(response.data || []);
+      });
     },
     methods: {
+      /** 合并字典与本地兜底类型,确保所有已知 logType 都有对应名称(与列表页保持一致) */
+      mergeLogTypeOptions(dictOptions) {
+        const localMap = {
+          1: '签到获得积分', 2: '消费获得积分', 3: '分享获得积分', 4: '退款扣除积分',
+          5: '购买积分商品', 6: '平台取消售后', 7: '积分过期', 8: '螳螂同步积分',
+          9: '购买课程小节扣除', 10: '观看课程获取积分', 11: '新用户完善就诊人获得积分',
+          12: '新用户填写收货地址获取积分', 13: '浏览商品获得积分', 14: '浏览视频获得积分',
+          15: '首次下单疗法获得积分', 16: '点播看课获得积分', 17: '点播答题获得积分',
+          18: '邀请新用户获取积分', 19: '填写邀请码获取积分', 20: '首次完成手机号绑定注册',
+          21: '首次完成专家咨询', 22: '首次完成积分商城下单', 23: '管理员添加',
+          24: '付费课程订阅', 25: '下单使用积分抵扣', 26: '取消订单退回积分', 27: '退款订单退回积分'
+        };
+        const result = [...dictOptions];
+        const existValues = new Set(dictOptions.map(o => Number(o.dictValue)));
+        Object.keys(localMap).forEach(key => {
+          const val = Number(key);
+          if (!existValues.has(val)) {
+            result.push({ dictValue: String(val), dictLabel: localMap[val] });
+          }
+        });
+        return result;
+      },
+      /** 加载当前订单的积分变动明细 */
+      loadIntegralLogs(orderId) {
+        this.logsLoading = true;
+        this.integralLogs = [];
+        getIntegralLogsByOrderId(orderId).then(response => {
+          this.integralLogs = response.data || [];
+          this.logsLoading = false;
+        }).catch(() => {
+          this.logsLoading = false;
+        });
+      },
       handlePhone(){
         const orderId = this.item.orderId;
         getOrderUserPhone(orderId).then(response =>{
@@ -271,11 +366,33 @@ import {getExpress, listIntegralOrder, sendgoods,getIntegralOrder, delIntegralOr
             },
       getDetails(orderId) {
         this.item=null;
+        this.integralLogs = [];
         getIntegralOrder(orderId).then(response => {
             this.item = response.data;
-            this.prod=[JSON.parse(this.item.itemJson)];
+            this.parseItemJson(this.item.itemJson);
+            // 同时加载当前订单的积分变动明细
+            this.loadIntegralLogs(orderId);
         });
       },
+      /**
+       * 解析积分订单 itemJson(兼容单对象和数组两种格式)
+       */
+      parseItemJson(itemJson) {
+        if (!itemJson) {
+          this.prod = [];
+          return;
+        }
+        try {
+          const parsed = JSON.parse(itemJson);
+          if (Array.isArray(parsed)) {
+            this.prod = parsed;
+          } else {
+            this.prod = [parsed];
+          }
+        } catch (e) {
+          this.prod = [];
+        }
+      },
     }
   }
 </script>
@@ -315,4 +432,16 @@ import {getExpress, listIntegralOrder, sendgoods,getIntegralOrder, delIntegralOr
   float: right;
   margin-right: 20px
 }
+.color-success {
+  color: #67c23a;
+}
+.font-small {
+  font-size: 14px;
+}
+.empty-tip {
+  text-align: center;
+  color: #909399;
+  padding: 20px 0;
+  font-size: 14px;
+}
 </style>

+ 133 - 0
src/views/components/his/shippingTemplateComponent.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="模板名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入模板名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table  height="500" border v-loading="loading" :data="shippingTemplatesList">
+      <el-table-column label="模板ID" align="center" prop="id" />
+      <el-table-column label="模板名称" align="center" prop="name" />
+      <el-table-column label="计费方式" align="center" prop="type" >
+          <template slot-scope="scope">
+              <el-tag prop="type" v-for="(item, index) in typeOptions"    v-if="scope.row.type==item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+      </el-table-column>
+      <el-table-column label="指定包邮开关" align="center" prop="appoint" >
+          <template slot-scope="scope">
+              <el-tag prop="type" v-for="(item, index) in appointOptions"    v-if="scope.row.appoint==item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+      </el-table-column>
+      <el-table-column label="添加时间" align="center" prop="createTime" />
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="selectTemplate(scope.row)"
+          >选择
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import { componentList, listShippingTemplates } from '@/api/hisStore/shippingTemplates'
+export default {
+  name: "ShippingTemplates",
+  data() {
+    return {
+      type:0,
+      appointList:[],
+      templateList:[],
+      appointOptions:[],
+      typeOptions:[],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 运费模板表格数据
+      shippingTemplatesList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        type: null,
+        regionInfo: null,
+        appoint: null,
+        appointInfo: null,
+        isDel: 0,
+        sort: null
+      },
+    };
+  },
+  created() {
+    this.getDicts("store_shipping_type").then((response) => {
+      this.typeOptions = response.data;
+    });
+    this.getDicts("store_shipping_appoint").then((response) => {
+      this.appointOptions = response.data;
+    });
+    this.getList();
+  },
+  methods: {
+    selectTemplate(row) {
+      this.$emit("select-template", {"id": row.id, "name": row.name });
+    },
+    /** 查询运费模板列表 */
+    getList() {
+      this.loading = true;
+      componentList(this.queryParams).then(response => {
+        this.shippingTemplatesList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+  }
+};
+</script>

+ 1 - 1
src/views/his/company/index.vue

@@ -628,7 +628,7 @@
             </el-form-item>
 
             <el-form-item label="分账百分比%" v-if="revenueForm.percentageFlag == 'Y'" :prop="`acctInfos.${index}.percentageDiv`"  >
-              <el-input-number v-model="account.percentageDiv" :precision="2" :step="0.1" :min="0" :max="100" placeholder="示例值:23.50,表示23.50%。acct_infos中全部分账百分比之和必须为100.00%。"></el-input-number>
+              <el-input-number v-model="account.percentageDiv" :precision="6" :step="0.1" :min="0" :max="100" placeholder="示例值:23.500000,表示23.50%。最多支持6位小数,acct_infos中全部分账百分比之和必须为100.000000%。"></el-input-number>
             </el-form-item>
             <el-form-item label="分账金额" v-if="revenueForm.percentageFlag == 'N'" :prop="`acctInfos.${index}.divAmt`"  >
               <el-input-number v-model="account.divAmt" :precision="2" :step="1" :min="0.01" placeholder="单位元,需保留小数点后两位,示例值:1.00 ,最低传入0.01"></el-input-number>

+ 487 - 0
src/views/his/integralAfterSales/components/integralAfterSalesDetails.vue

@@ -0,0 +1,487 @@
+<template>
+  <div class="order-content" v-loading="loading">
+    <!-- 售后流程步骤条 -->
+    <div class="order-status" v-if="afterSales != null">
+      <!-- 仅退款:用户提交 → 平台审核 → 财务审核 → 退款成功 -->
+      <el-steps v-if="isRefundOnly" :active="refundOnlyActive" align-center finish-status="success">
+        <el-step title="用户提交"></el-step>
+        <el-step title="平台审核"></el-step>
+        <el-step title="财务审核"></el-step>
+        <el-step title="退款成功"></el-step>
+      </el-steps>
+      <!-- 退款退货:用户提交 → 平台审核 → 用户发货 → 仓库审核 → 财务审核 → 退款成功 -->
+      <el-steps v-else :active="refundReturnActive" align-center finish-status="success">
+        <el-step title="用户提交"></el-step>
+        <el-step title="平台审核"></el-step>
+        <el-step title="用户发货"></el-step>
+        <el-step title="仓库审核"></el-step>
+        <el-step title="财务审核"></el-step>
+        <el-step title="退款成功"></el-step>
+      </el-steps>
+    </div>
+
+    <div v-if="afterSales != null">
+      <!-- 操作按钮区 -->
+      <el-card shadow="never" style="margin-top: 15px">
+        <div class="operate-container">
+          <span style="margin-left: 20px" class="color-danger">状态:
+            <el-tag v-for="item in statusOptions" :key="item.dictValue"
+                    v-if="afterSales.status == item.dictValue" :type="statusTagType(afterSales.status)">{{ item.dictLabel }}</el-tag>
+          </span>
+          <div class="operate-button-container">
+            <!-- 平台审核:status=0 -->
+            <el-button size="mini" v-hasPermi="['his:integralAfterSales:audit']"
+                       v-if="Number(afterSales.salesStatus) === 0 && Number(afterSales.status) === 0"
+                       @click="handleAudit">平台审核</el-button>
+            <!-- 编辑物流:退款退货且status=1,平台填写快递信息 -->
+            <el-button size="mini" v-hasPermi="['his:integralAfterSales:edit']"
+                       v-if="Number(afterSales.salesStatus) === 0 && Number(afterSales.status) === 1 && !isRefundOnly"
+                       @click="addDelivery">编辑物流</el-button>
+            <!-- 仓库审核:退款退货且status=2 -->
+            <el-button size="mini" v-hasPermi="['his:integralAfterSales:audit']"
+                       v-if="Number(afterSales.salesStatus) === 0 && Number(afterSales.status) === 2 && !isRefundOnly"
+                       @click="handleWarehouseAudit">仓库审核</el-button>
+            <!-- 财务退款:仅退款status=1 或 退款退货status=3 -->
+            <el-button size="mini" type="danger" v-hasPermi="['his:integralAfterSales:refund']"
+                       v-if="Number(afterSales.salesStatus) === 0 && canRefund"
+                       @click="handleRefund">财务退款</el-button>
+            <!-- 撤销:未进入退款流程(status < 4) -->
+            <el-button size="mini" v-hasPermi="['his:integralAfterSales:cancel']"
+                       v-if="Number(afterSales.salesStatus) === 0 && Number(afterSales.status) < 4"
+                       @click="handleCancel">撤销</el-button>
+            <!-- 查看寄回物流 -->
+            <el-button size="mini" v-if="afterSales.deliverySn != null && afterSales.deliverySn !== ''"
+                       @click="showExpress">查看寄回物流</el-button>
+          </div>
+        </div>
+
+        <!-- 售后基本信息 -->
+        <div class="section-title">售后基本信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="售后单号">{{ afterSales.id }}</el-descriptions-item>
+          <el-descriptions-item label="订单编号">{{ afterSales.orderCode }}</el-descriptions-item>
+          <el-descriptions-item label="服务类型">
+            <el-tag v-for="item in serviceTypeOptions" :key="item.dictValue"
+                    v-if="afterSales.serviceType == item.dictValue">{{ item.dictLabel }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="退款金额">
+            <span v-if="afterSales.refundAmount != null">¥{{ afterSales.refundAmount }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="退回积分">
+            <span v-if="afterSales.refundIntegral != null">{{ afterSales.refundIntegral }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="状态">
+            <el-tag v-for="item in statusOptions" :key="item.dictValue"
+                    v-if="afterSales.status == item.dictValue" :type="statusTagType(afterSales.status)">{{ item.dictLabel }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="申请原因">{{ afterSales.reasons }}</el-descriptions-item>
+          <el-descriptions-item label="说明">{{ afterSales.explains }}</el-descriptions-item>
+          <el-descriptions-item label="操作人">{{ afterSales.operator }}</el-descriptions-item>
+          <el-descriptions-item label="提交时间">{{ afterSales.createTime }}</el-descriptions-item>
+          <el-descriptions-item label="备注">{{ afterSales.remark }}</el-descriptions-item>
+        </el-descriptions>
+        <div v-if="afterSales.explainImg != null && afterSales.explainImg !== ''" style="margin-top: 10px;">
+          <span class="font-small">说明图片:</span>
+          <el-image v-for="(url, index) in afterSales.explainImg.split(',')" :key="index"
+                    style="width: 100px; height: 100px; margin-right: 5px;"
+                    :src="url" :preview-src-list="afterSales.explainImg.split(',')" :initial-index="index">
+          </el-image>
+        </div>
+      </el-card>
+
+      <!-- 用户信息 -->
+      <el-card shadow="never" style="margin-top: 15px">
+        <div class="section-title">用户信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="用户ID">{{ afterSales.userId }}</el-descriptions-item>
+          <el-descriptions-item label="用户昵称">{{ afterSales.nickName }}</el-descriptions-item>
+          <el-descriptions-item label="用户电话">{{ afterSales.userPhone }}</el-descriptions-item>
+          <el-descriptions-item label="收货地址" :span="3">{{ afterSales.userAddress }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <!-- 支付信息 -->
+      <el-card shadow="never" style="margin-top: 15px">
+        <div class="section-title">支付信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="支付积分">{{ afterSales.integral }}</el-descriptions-item>
+          <el-descriptions-item label="支付金额">
+            <span v-if="afterSales.payMoney != null">¥{{ afterSales.payMoney }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="支付方式">
+            <span v-if="afterSales.payType == 1">积分</span>
+            <span v-else-if="afterSales.payType == 2">现金</span>
+            <span v-else-if="afterSales.payType == 3">积分+现金</span>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="下单时间">{{ afterSales.orderCreateTime }}</el-descriptions-item>
+          <el-descriptions-item label="支付时间">{{ afterSales.payTime }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <!-- 积分订单商品信息 -->
+      <el-card shadow="never" style="margin-top: 15px">
+        <div class="section-title">积分详情 / 商品信息</div>
+        <el-table border :data="prodList" size="small" style="width: 100%; margin-top: 10px">
+          <el-table-column label="商品图片" width="120" align="center">
+            <template slot-scope="scope">
+              <img v-if="scope.row.imgUrl" :src="scope.row.imgUrl" style="height: 70px" />
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="商品名称" align="center" prop="goodsName" show-overflow-tooltip>
+            <template slot-scope="scope">{{ scope.row.goodsName }}</template>
+          </el-table-column>
+          <el-table-column label="商品编号" width="180" align="center" prop="barCode">
+            <template slot-scope="scope">{{ scope.row.barCode || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="数量" width="100" align="center" prop="num">
+            <template slot-scope="scope">{{ scope.row.num || 1 }}</template>
+          </el-table-column>
+          <el-table-column label="原价" width="120" align="center" prop="otPrice">
+            <template slot-scope="scope">
+              <span v-if="scope.row.otPrice != null">¥{{ scope.row.otPrice }}</span>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="积分" width="120" align="center" prop="integral">
+            <template slot-scope="scope">{{ scope.row.integral }}</template>
+          </el-table-column>
+          <el-table-column label="现金" width="120" align="center" prop="cash">
+            <template slot-scope="scope">
+              <span v-if="scope.row.cash != null">¥{{ scope.row.cash }}</span>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <div class="section-title" style="margin-top: 20px">订单发货物流</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="快递公司">{{ afterSales.orderDeliveryName }}</el-descriptions-item>
+          <el-descriptions-item label="快递单号">{{ afterSales.orderDeliverySn }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <!-- 用户寄回物流 -->
+      <el-card shadow="never" style="margin-top: 15px" v-if="afterSales.deliverySn != null && afterSales.deliverySn !== ''">
+        <div class="section-title">用户寄回物流</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="物流公司">{{ afterSales.deliveryName }}</el-descriptions-item>
+          <el-descriptions-item label="物流单号">{{ afterSales.deliverySn }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+    </div>
+
+    <!-- 编辑物流弹窗 -->
+    <el-dialog title="填写用户寄回物流" :visible.sync="deliveryOpen" width="500px" append-to-body>
+      <el-form ref="deliveryForm" :model="deliveryForm" :rules="deliveryRules" label-width="100px">
+        <el-form-item label="物流公司" prop="deliveryName">
+          <el-input v-model="deliveryForm.deliveryName" placeholder="请输入物流公司名称" />
+        </el-form-item>
+        <el-form-item label="物流单号" prop="deliverySn">
+          <el-input v-model="deliveryForm.deliverySn" placeholder="请输入物流单号" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitDelivery">确 定</el-button>
+        <el-button @click="deliveryOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 查看寄回物流弹窗 -->
+    <el-dialog title="用户寄回物流信息" :visible.sync="expressOpen" width="720px" append-to-body>
+      <el-descriptions :column="1" border v-if="afterSales != null">
+        <el-descriptions-item label="物流公司">{{ afterSales.deliveryName }}</el-descriptions-item>
+        <el-descriptions-item label="物流单号">{{ afterSales.deliverySn }}</el-descriptions-item>
+      </el-descriptions>
+      <div style="margin-top: 20px; font-weight: bold;">物流流转信息</div>
+      <div v-loading="expressLoading">
+        <el-table v-if="traces && traces.length > 0"
+                  style="margin-top: 10px; width: 100%"
+                  :data="traces" border size="small">
+          <el-table-column label="时间" width="170" align="center">
+            <template slot-scope="scope">{{ scope.row.AcceptTime }}</template>
+          </el-table-column>
+          <el-table-column label="位置" align="center">
+            <template slot-scope="scope">{{ scope.row.Location }}</template>
+          </el-table-column>
+          <el-table-column label="描述" align="center">
+            <template slot-scope="scope">{{ scope.row.AcceptStation }}</template>
+          </el-table-column>
+        </el-table>
+        <div v-else-if="!expressLoading"
+             style="margin-top: 10px; padding: 24px 0; text-align: center; color: #909399;">
+          暂无物流信息
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="expressOpen = false">关 闭</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  getIntegralAfterSales,
+  getIntegralAfterSalesByOrderCode,
+  auditIntegralAfterSales,
+  warehouseAuditIntegralAfterSales,
+  cancelIntegralAfterSales,
+  refundIntegralAfterSales,
+  updateIntegralAfterSales,
+  getAfterSalesExpress
+} from "@/api/his/integralAfterSales";
+
+export default {
+  name: "IntegralAfterSalesDetails",
+  data() {
+    return {
+      loading: false,
+      afterSales: null,
+      prodList: [],
+      // 字典
+      statusOptions: [],
+      serviceTypeOptions: [],
+      // 编辑物流
+      deliveryOpen: false,
+      deliveryForm: {
+        id: null,
+        deliveryName: null,
+        deliverySn: null
+      },
+      deliveryRules: {
+        deliveryName: [{ required: true, message: '请输入物流公司名称', trigger: 'blur' }],
+        deliverySn: [{ required: true, message: '请输入物流单号', trigger: 'blur' }]
+      },
+      // 查看物流
+      expressOpen: false,
+      traces: [],
+      expressLoading: false
+    };
+  },
+  computed: {
+    /** 是否仅退款(serviceType=0) */
+    isRefundOnly() {
+      return this.afterSales != null && Number(this.afterSales.serviceType) === 0;
+    },
+    /** 仅退款步骤条active值:0→0, 1→1, 4→3(财务审核), 5→4(完成) */
+    refundOnlyActive() {
+      const status = Number(this.afterSales.status);
+      if (status >= 5) return 4;
+      if (status >= 4) return 3;
+      return status;
+    },
+    /** 退款退货步骤条active值:0→0, 1→1, 2→2, 3→3, 4→4, 5→6(完成) */
+    refundReturnActive() {
+      const status = Number(this.afterSales.status);
+      if (status >= 5) return 6;
+      return status;
+    },
+    /** 是否可执行财务退款:仅退款status=1 或 退款退货status=3 */
+    canRefund() {
+      if (!this.afterSales) return false;
+      const status = Number(this.afterSales.status);
+      if (this.isRefundOnly) {
+        return status === 1;
+      }
+      return status === 3;
+    }
+  },
+  created() {
+    this.getDicts("integral_after_sales_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("store_after_sales_service_type").then(response => {
+      this.serviceTypeOptions = response.data;
+    });
+  },
+  methods: {
+    /** 状态标签类型 */
+    statusTagType(status) {
+      const typeMap = { 0: 'info', 1: 'warning', 2: 'warning', 3: 'primary', 4: 'danger', 5: 'success' };
+      return typeMap[status] || 'info';
+    },
+    /** 获取详情 */
+    getDetails(id) {
+      this.loading = true;
+      this.afterSales = null;
+      this.prodList = [];
+      getIntegralAfterSales(id).then(response => {
+        this.afterSales = response.data;
+        this.parseItemJson(this.afterSales.itemJson);
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    /** 根据订单编号查询售后详情 */
+    getDetailsByOrderCode(orderCode) {
+      this.loading = true;
+      this.afterSales = null;
+      this.prodList = [];
+      getIntegralAfterSalesByOrderCode(orderCode).then(response => {
+        if (response.data) {
+          this.afterSales = response.data;
+          this.parseItemJson(this.afterSales.itemJson);
+        } else {
+          this.afterSales = null;
+          this.prodList = [];
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    /**
+     * 解析积分订单 itemJson(兼容单对象和数组两种格式)
+     */
+    parseItemJson(itemJson) {
+      if (!itemJson) {
+        this.prodList = [];
+        return;
+      }
+      try {
+        const parsed = JSON.parse(itemJson);
+        if (Array.isArray(parsed)) {
+          this.prodList = parsed;
+        } else {
+          this.prodList = [parsed];
+        }
+      } catch (e) {
+        this.prodList = [];
+      }
+    },
+    /** 平台审核 */
+    handleAudit() {
+      this.$confirm('确认平台审核通过?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        auditIntegralAfterSales({ id: this.afterSales.id }).then(() => {
+          this.msgSuccess("审核成功");
+          this.getDetails(this.afterSales.id);
+          this.$parent.$parent.getList();
+        });
+      }).catch(() => {});
+    },
+    /** 仓库审核 */
+    handleWarehouseAudit() {
+      this.$confirm('确认仓库审核通过?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        warehouseAuditIntegralAfterSales({ id: this.afterSales.id }).then(() => {
+          this.msgSuccess("仓库审核成功");
+          this.getDetails(this.afterSales.id);
+          this.$parent.$parent.getList();
+        });
+      }).catch(() => {});
+    },
+    /** 撤销 */
+    handleCancel() {
+      this.$confirm('确认撤销售后单?撤销后无法恢复', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        cancelIntegralAfterSales({ id: this.afterSales.id }).then(() => {
+          this.msgSuccess("撤销成功");
+          this.getDetails(this.afterSales.id);
+          this.$parent.$parent.getList();
+        });
+      }).catch(() => {});
+    },
+    /** 财务退款 */
+    handleRefund() {
+      this.$confirm('确认执行财务退款?将原路退回用户资金并退回积分', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        refundIntegralAfterSales({ id: this.afterSales.id }).then(() => {
+          this.msgSuccess("退款已发起,等待回调确认");
+          this.getDetails(this.afterSales.id);
+          this.$parent.$parent.getList();
+        });
+      }).catch(() => {});
+    },
+    /** 编辑物流 */
+    addDelivery() {
+      this.deliveryForm = {
+        id: this.afterSales.id,
+        deliveryName: this.afterSales.deliveryName,
+        deliverySn: this.afterSales.deliverySn
+      };
+      this.deliveryOpen = true;
+    },
+    submitDelivery() {
+      this.$refs["deliveryForm"].validate(valid => {
+        if (valid) {
+          updateIntegralAfterSales(this.deliveryForm).then(() => {
+            this.msgSuccess("保存成功");
+            this.deliveryOpen = false;
+            this.getDetails(this.afterSales.id);
+            this.$parent.$parent.getList();
+          });
+        }
+      });
+    },
+    /** 查看寄回物流 */
+    showExpress() {
+      this.expressOpen = true;
+      this.traces = [];
+      if (this.afterSales && this.afterSales.deliverySn) {
+        this.expressLoading = true;
+        getAfterSalesExpress(this.afterSales.id).then(response => {
+          if (response && response.data && response.data.Traces) {
+            this.traces = response.data.Traces;
+          } else {
+            this.traces = [];
+          }
+        }).finally(() => {
+          this.expressLoading = false;
+        });
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.order-content {
+  padding: 0 20px 20px;
+}
+.section-title {
+  padding: 15px 0;
+  color: #524b4a;
+  font-weight: bold;
+  font-size: 14px;
+}
+.operate-container {
+  background: #F2F6FC;
+  height: 60px;
+  margin: -20px -20px 0;
+  line-height: 60px;
+}
+.operate-button-container {
+  float: right;
+  margin-right: 20px;
+}
+.color-danger {
+  color: #f56c6c;
+}
+.font-small {
+  font-size: 14px;
+  color: #606266;
+}
+</style>
+
+<style>
+.order-content .el-descriptions-item__label.is-bordered-label {
+  font-weight: normal;
+  width: 120px;
+}
+</style>

+ 301 - 0
src/views/his/integralAfterSales/index.vue

@@ -0,0 +1,301 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+      <el-form-item label="订单编号" prop="orderCode">
+        <el-input
+          v-model="queryParams.orderCode"
+          placeholder="请输入订单编号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入用户昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="所属公司" prop="companyId">
+        <el-select v-model="queryParams.companyId" placeholder="请选择公司" clearable filterable size="small">
+          <el-option
+            v-for="item in companyList"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="服务类型" prop="serviceType">
+        <el-select v-model="queryParams.serviceType" placeholder="请选择服务类型" clearable size="small">
+          <el-option
+            v-for="item in serviceTypeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option
+            v-for="item in statusOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="物流单号" prop="deliverySn">
+        <el-input
+          v-model="queryParams.deliverySn"
+          placeholder="请输入物流单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="提交时间" prop="createTime">
+        <el-date-picker
+          v-model="dateRange"
+          size="small"
+          style="width: 220px"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['his:integralAfterSales:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="afterSalesList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="售后单号" align="center" prop="id" width="90" />
+      <el-table-column label="订单编号" align="center" prop="orderCode" show-overflow-tooltip />
+      <el-table-column label="用户昵称" align="center" prop="nickName" />
+      <el-table-column label="用户电话" align="center" prop="userPhone" />
+      <el-table-column label="所属公司" align="center" prop="companyName" show-overflow-tooltip />
+      <el-table-column label="退款金额" align="center" prop="refundAmount">
+        <template slot-scope="scope">
+          <span v-if="scope.row.refundAmount != null">¥{{ scope.row.refundAmount }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="退回积分" align="center" prop="refundIntegral">
+        <template slot-scope="scope">
+          <span v-if="scope.row.refundIntegral != null">{{ scope.row.refundIntegral }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="服务类型" align="center" prop="serviceType" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="getDictLabel(serviceTypeOptions, scope.row.serviceType)"
+                  :type="serviceTypeTagType(scope.row.serviceType)">
+            {{ getDictLabel(serviceTypeOptions, scope.row.serviceType) }}
+          </el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="getDictLabel(statusOptions, scope.row.status)"
+                  :type="statusTagType(scope.row.status)">
+            {{ getDictLabel(statusOptions, scope.row.status) }}
+          </el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="物流单号" align="center" prop="deliverySn" show-overflow-tooltip />
+      <el-table-column label="提交时间" align="center" prop="createTime" width="160" />
+      <el-table-column label="操作" align="center" fixed="right" width="100" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleShow(scope.row)"
+            v-hasPermi="['his:integralAfterSales:query']"
+          >查看</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 售后详情抽屉 -->
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      :title="drawerTitle"
+      :visible.sync="drawerOpen"
+      @close="getList"
+    >
+      <integral-after-sales-details ref="details" />
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import {
+  listIntegralAfterSales,
+  exportIntegralAfterSales
+} from "@/api/his/integralAfterSales";
+import { getCompanyList } from "@/api/company/company";
+import integralAfterSalesDetails from "./components/integralAfterSalesDetails";
+
+export default {
+  name: "IntegralAfterSales",
+  components: { integralAfterSalesDetails },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 售后单列表
+      afterSalesList: [],
+      // 选中数组
+      ids: [],
+      // 单个禁用
+      single: true,
+      // 多个禁用
+      multiple: true,
+      // 日期范围
+      dateRange: [],
+      // 抽屉
+      drawerOpen: false,
+      drawerTitle: "售后订单详情",
+      // 公司下拉
+      companyList: [],
+      // 字典
+      statusOptions: [],
+      serviceTypeOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        orderCode: null,
+        nickName: null,
+        companyId: null,
+        serviceType: null,
+        status: null,
+        deliverySn: null,
+        beginTime: null,
+        endTime: null
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.getDicts("integral_after_sales_status").then(response => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("store_after_sales_service_type").then(response => {
+      this.serviceTypeOptions = response.data;
+    });
+    getCompanyList().then(response => {
+      this.companyList = response.data;
+    });
+  },
+  methods: {
+    /** 获取字典标签(无匹配返回空串) */
+    getDictLabel(options, value) {
+      if (value === null || value === undefined || value === '') return '';
+      const item = options.find(d => d.dictValue == value);
+      return item ? item.dictLabel : '';
+    },
+    /** 状态标签类型 */
+    statusTagType(status) {
+      const typeMap = { 0: 'info', 1: 'warning', 2: 'warning', 3: 'primary', 4: 'danger', 5: 'success' };
+      return typeMap[status] || 'info';
+    },
+    /** 服务类型标签类型 */
+    serviceTypeTagType(serviceType) {
+      const typeMap = { 0: 'warning', 1: 'danger' };
+      return typeMap[serviceType] || 'info';
+    },
+    /** 查询售后单列表 */
+    getList() {
+      this.loading = true;
+      const params = this.addDateRange(this.queryParams, this.dateRange);
+      listIntegralAfterSales(params).then(response => {
+        this.afterSalesList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.resetForm("queryForm");
+      this.queryParams.beginTime = null;
+      this.queryParams.endTime = null;
+      this.handleQuery();
+    },
+    /** 多选框选中数据 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 查看详情 */
+    handleShow(row) {
+      this.drawerOpen = true;
+      const id = row.id;
+      setTimeout(() => {
+        this.$refs.details.getDetails(id);
+      }, 200);
+    },
+    /** 导出 */
+    handleExport() {
+      this.exportLoading = true;
+      const params = this.addDateRange(this.queryParams, this.dateRange);
+      exportIntegralAfterSales(params).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+        this.exportLoading = false;
+      });
+    }
+  }
+};
+</script>

+ 58 - 21
src/views/his/integralOrder/index.vue

@@ -52,7 +52,7 @@
        <el-form-item label="提交时间" prop="createTime">
                  <el-date-picker v-model="createTime" size="small" style="width: 220px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" @change="change"></el-date-picker>
               </el-form-item>
-      <el-form-item label="销售公司" prop="companyId">
+      <!-- <el-form-item label="销售公司" prop="companyId">
         <el-select v-model="queryParams.companyId" placeholder="销售公司"  size="small" @change="getAllUserlist(queryParams.companyId)" clearable>
           <el-option
             v-for="dict in qwCompanyList"
@@ -81,7 +81,7 @@
             :value="dict.id"
           />
         </el-select>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -113,10 +113,6 @@
 
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
-    <el-tabs type="card" v-model="actName" @tab-click="handleClickX">
-      <el-tab-pane label="全部订单" name="10"></el-tab-pane>
-      <el-tab-pane v-for="(item,index) in statusOptions" :label="item.dictLabel" :name="item.dictValue"></el-tab-pane>
-    </el-tabs>
     <el-table v-loading="loading" border :data="integralOrderList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="订单编号" align="center" prop="orderCode" />
@@ -124,7 +120,7 @@
       <el-table-column label="用户电话" align="center" prop="userPhone" />
       <el-table-column label="用户地址" align="center" prop="userAddress" show-overflow-tooltip />
       <el-table-column label="支付积分" align="center" prop="integral" />
-      <el-table-column label="支付金额" align="center" prop="cash" />
+      <el-table-column label="支付金额" align="center" prop="payMoney" />
       <el-table-column label="状态" align="center" prop="status">
         <template slot-scope="scope">
           <dict-tag :options="statusOptions" :value="scope.row.status"/>
@@ -135,11 +131,11 @@
       <el-table-column label="快递单号" align="center" prop="deliverySn" />
       <el-table-column label="发货时间" align="center" prop="deliveryTime" width="180"/>
       <el-table-column label="提交时间" align="center" prop="createTime" width="180"/>
-      <el-table-column label="销售公司ID" align="center" prop="companyId" width="180"/>
+      <!-- <el-table-column label="销售公司ID" align="center" prop="companyId" width="180"/>
       <el-table-column label="销售ID" align="center" prop="companyUserId" width="180"/>
-      <el-table-column label="企业微信ID" align="center" prop="qwUserId" width="180"/>
+      <el-table-column label="企业微信ID" align="center" prop="qwUserId" width="180"/> -->
       <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip/>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="220">
         <template slot-scope="scope">
           <el-button
              size="mini"
@@ -147,6 +143,23 @@
              @click="handledetails(scope.row)"
              >详情
           </el-button>
+          <el-button
+             v-if="scope.row.status === 2"
+             size="mini"
+             type="text"
+             @click="handleFinishOrder(scope.row)"
+             v-hasPermi="['his:integralOrder:finish']"
+             >确认收货
+          </el-button>
+          <!-- 退款中和已退款状态显示售后单状态 -->
+          <el-button
+             v-if="scope.row.status === -1 || scope.row.status === -2"
+             size="mini"
+             type="text"
+             @click="handleShowAfterSales(scope.row)"
+             v-hasPermi="['his:integralAfterSales:query']"
+             >售后详情
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -225,6 +238,14 @@
              :title="show.title" :visible.sync="show.open">
          <integralOrderDetails  ref="Details" />
        </el-drawer>
+    <!-- 售后详情抽屉 -->
+    <el-drawer
+      :with-header="false"
+      size="75%"
+      title="售后订单详情"
+      :visible.sync="afterSalesDrawer.open">
+      <integral-after-sales-details ref="afterSalesDetails" />
+    </el-drawer>
    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
          <el-upload
            ref="upload"
@@ -256,8 +277,9 @@
 </template>
 
 <script>
-import {importTemplate, listIntegralOrder,importExpressTemplate, getIntegralOrder, delIntegralOrder, addIntegralOrder, updateIntegralOrder, exportIntegralOrder } from "@/api/his/integralOrder";
+import {importTemplate, listIntegralOrder,importExpressTemplate, getIntegralOrder, delIntegralOrder, addIntegralOrder, updateIntegralOrder, exportIntegralOrder, finishOrder } from "@/api/his/integralOrder";
 import integralOrderDetails from '../../components/his/integralOrderDetails.vue';
+import integralAfterSalesDetails from '../integralAfterSales/components/integralAfterSalesDetails.vue';
 import { getToken } from "@/utils/auth";
 import {getCompanyList} from "@/api/company/company";
 import {getAllUserlist} from "@/api/company/companyUser";
@@ -265,12 +287,15 @@ import {getQwUserInfo} from "@/api/qw/qwUser";
 
 export default {
   name: "IntegralOrder",
-  components: { integralOrderDetails },
+  components: { integralOrderDetails, integralAfterSalesDetails },
   data() {
     return {
       show:{
               open:false,
             },
+      afterSalesDrawer: {
+        open: false
+      },
       upload: {
         // 是否显示弹出层
         open: false,
@@ -287,7 +312,6 @@ export default {
       },
       // 遮罩层
       loading: true,
-      actName:"10",
       // 导出遮罩层
       exportLoading: false,
       // 选中数组
@@ -355,6 +379,20 @@ export default {
     });
   },
   methods: {
+    /** 确认收货(status: 2 待收货 → 3 已完成) */
+    handleFinishOrder(row) {
+      const orderCode = row.orderCode;
+      this.$confirm('确定确认收货?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        finishOrder(orderCode).then(() => {
+          this.$message({ type: 'success', message: '确认收货成功!' });
+          this.getList();
+        });
+      }).catch(() => {});
+    },
     change(){
           if(this.createTime!=null){
             this.queryParams.sTime=this.createTime[0];
@@ -396,6 +434,13 @@ export default {
              this.$refs.Details.getDetails(row.orderId);
         }, 1);
     },
+    /** 查看售后详情 */
+    handleShowAfterSales(row) {
+      this.afterSalesDrawer.open = true;
+      setTimeout(() => {
+        this.$refs.afterSalesDetails.getDetailsByOrderCode(row.orderCode);
+      }, 200);
+    },
     /** 查询积分商品订单列表 */
     getList() {
       this.loading = true;
@@ -492,14 +537,6 @@ export default {
         }
       });
     },
-    handleClickX(tab, event) {
-     if(tab.name=="10"){
-       this.queryParams.status=null;
-     }else{
-       this.queryParams.status=tab.name;
-     }
-      this.handleQuery();
-    },
     /** 删除按钮操作 */
     handleDelete(row) {
       const orderIds = row.orderId || this.ids;

+ 21 - 4
src/views/hisStore/integralGoods/index.vue

@@ -122,8 +122,15 @@
         </template>
       </el-table-column>
       <el-table-column label="所需积分" align="center" prop="integral" />
+      <el-table-column label="需支付金额" align="center" prop="cash" />
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="库存" align="center" prop="stock" />
+      <el-table-column label="限购" align="center" prop="limitNum">
+        <template slot-scope="scope">
+          <span v-if="scope.row.limitNum > 0">{{ scope.row.limitNum }}件/人</span>
+          <span v-else>不限购</span>
+        </template>
+      </el-table-column>
       <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="150">
         <template slot-scope="scope">
@@ -188,6 +195,9 @@
         <el-form-item label="所需积分" prop="integral">
           <el-input-number v-model="form.integral"  :min="0"  label="所需积分"></el-input-number>
         </el-form-item>
+        <el-form-item label="支付金额" prop="cash">
+          <el-input-number v-model="form.cash"  :min="0" :precision="2" :step="0.1"  label="需支付金额"></el-input-number>
+        </el-form-item>
         <el-form-item label="商品编号" prop="barCode">
           <el-input v-model="form.barCode" placeholder="请输入商品编号"  style="width: 200px;"/>
         </el-form-item>
@@ -202,6 +212,10 @@
         <el-form-item label="库存" prop="stock">
           <el-input-number v-model="form.stock"  :min="0"  label="库存"></el-input-number>
         </el-form-item>
+        <el-form-item label="限购数量" prop="limitNum">
+          <el-input-number v-model="form.limitNum" :min="0" :step="1" :precision="0" label="限购数量"></el-input-number>
+          <span style="margin-left: 10px; color: #909399; font-size: 12px;">0表示不限购,大于0表示每用户限购件数</span>
+        </el-form-item>
         <el-form-item label="状态">
           <el-radio-group v-model="form.status">
             <el-radio
@@ -251,8 +265,6 @@
             <el-button @click="upload.open = false">取 消</el-button>
           </div>
         </el-dialog>
-
-
   </div>
 </template>
 
@@ -263,8 +275,8 @@ import Editor from '@/components/Editor/wang';
 import { getToken } from "@/utils/auth";
 import integralGoodsDetails from '../components/integralGoodsDetails';
 export default {
-  name: "HisIntegralGoods",
-  components: {Material,Editor,integralGoodsDetails},
+  name: "IntegralGoods",
+  components: { Material,Editor,integralGoodsDetails},
   watch: {
     imageArr: function(val) {
       this.form.imgUrl = val.join(',')
@@ -412,11 +424,15 @@ export default {
         goodsType: null,
         status: 0,
         integral: null,
+        cash: null,
         sort: null,
         stock: null,
+        limitNum: 0,
         descs: null,
         createTime: null
       };
+      this.imageArr = []
+      this.photoArr = []
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -460,6 +476,7 @@ export default {
          if(this.form.images!=null){
              this.photoArr=this.form.images.split(",");
            }
+
         //修改按钮
         setTimeout(() => {
                   if(this.form.descs==null){

+ 88 - 23
src/views/system/config/config.vue

@@ -430,28 +430,28 @@
         </el-form>
       </el-tab-pane>
 
-      <el-tab-pane label="积分比例配置" name="store.integral">
-        <el-form ref="form24" :model="form24" :rules="rules3" label-width="160px">
-          <el-form-item label="积分抵用比例" prop="integralRatio">
-            <el-tooltip class="item" effect="dark" content="1积分抵多少金额" placement="top-end">
-              <el-input-number v-model="form24.integralRatio"></el-input-number>
-            </el-tooltip>
-          </el-form-item>
-          <el-form-item label="满多少可以抵扣" prop="integralFull">
-            <el-tooltip class="item" effect="dark" content="消费必须满一定额度才可使用,0代表无限制" placement="top-end">
-              <el-input-number v-model="form24.integralFull"></el-input-number>
-            </el-tooltip>
-          </el-form-item>
-          <el-form-item label="单次最大抵扣积分" prop="integralMax">
-            <el-tooltip class="item" effect="dark" content="限制一次只能使用多少积分,0代表无限制" placement="top-end">
-              <el-input-number v-model="form24.integralMax"></el-input-number>
-            </el-tooltip>
-          </el-form-item>
-          <div class="footer">
-            <el-button type="primary" @click="submitForm24">提 交</el-button>
-          </div>
-        </el-form>
-      </el-tab-pane>
+<!--      <el-tab-pane label="积分比例配置" name="store.integral">-->
+<!--        <el-form ref="form24" :model="form24" :rules="rules3" label-width="160px">-->
+<!--          <el-form-item label="积分抵用比例" prop="integralRatio">-->
+<!--            <el-tooltip class="item" effect="dark" content="1积分抵多少金额" placement="top-end">-->
+<!--              <el-input-number v-model="form24.integralRatio"></el-input-number>-->
+<!--            </el-tooltip>-->
+<!--          </el-form-item>-->
+<!--          <el-form-item label="满多少可以抵扣" prop="integralFull">-->
+<!--            <el-tooltip class="item" effect="dark" content="消费必须满一定额度才可使用,0代表无限制" placement="top-end">-->
+<!--              <el-input-number v-model="form24.integralFull"></el-input-number>-->
+<!--            </el-tooltip>-->
+<!--          </el-form-item>-->
+<!--          <el-form-item label="单次最大抵扣积分" prop="integralMax">-->
+<!--            <el-tooltip class="item" effect="dark" content="限制一次只能使用多少积分,0代表无限制" placement="top-end">-->
+<!--              <el-input-number v-model="form24.integralMax"></el-input-number>-->
+<!--            </el-tooltip>-->
+<!--          </el-form-item>-->
+<!--          <div class="footer">-->
+<!--            <el-button type="primary" @click="submitForm24">提 交</el-button>-->
+<!--          </div>-->
+<!--        </el-form>-->
+<!--      </el-tab-pane>-->
 
       <el-tab-pane label="套餐包" name="his.package">
         <el-form ref="form8" :model="form8" label-width="150px">
@@ -576,6 +576,9 @@
           <el-form-item v-if="form9.type=='yb'" label="退款回调地址" prop="ybRefundNotifyUrl">
             <el-input v-model="form9.ybRefundNotifyUrl" placeholder="聚合支付退款回调地址"></el-input>
           </el-form-item>
+          <el-form-item v-if="form9.type=='yb'" label="积分订单退款回调地址" prop="ybIntegralRefundNotifyUrl">
+            <el-input v-model="form9.ybIntegralRefundNotifyUrl" placeholder="积分订单专属退款回调地址(与普通商品退款回调隔离)"></el-input>
+          </el-form-item>
           <el-form-item v-if="form9.type=='yb'" label="分账回调地址" prop="ybDivideNotifyUrl">
             <el-input v-model="form9.ybDivideNotifyUrl" placeholder="聚合支付分账回调地址"></el-input>
           </el-form-item>
@@ -2388,6 +2391,25 @@
         </el-form>
       </el-tab-pane>
 
+      <el-tab-pane label="药师端配置" name="his.doctorConfig">
+        <el-form ref="form29" :model="form29" label-width="200px">
+          <el-form-item label="药师签名上传页面地址:" prop="signPageUrl">
+            <el-input
+              v-model="form29.signPageUrl"
+              placeholder="请输入药师签名上传H5页面地址,例如:http://api.example.com/doctor-sign.html"
+              clearable
+            />
+            <div style="color: #909399; font-size: 12px; line-height: 1.6; margin-top: 4px;">
+              药师端PC点击"扫码上传签名"按钮时,将基于此地址生成二维码(自动拼接 token 参数,5分钟有效期),药师用手机扫码后跳转至该H5页面进行签名上传。<br/>
+              默认后端 fs-doctor-app 已内置 H5 页面,部署后访问地址形如:http://{api域名}/doctor-sign.html
+            </div>
+          </el-form-item>
+          <div class="footer">
+            <el-button type="primary" @click="submitForm29">提 交</el-button>
+          </div>
+        </el-form>
+      </el-tab-pane>
+
     </el-tabs>
 
 
@@ -2403,7 +2425,7 @@
 </template>
 
 <script>
-import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn } from '@/api/system/config'
+import { getConfigByKey, updateConfigByKey, clearCache, updateIsTownOn, addConfig } from '@/api/system/config'
 import { listStore } from '@/api/his/storeProduct'
 import { js } from 'js-beautify'
 import Material from '@/components/Material'
@@ -2574,6 +2596,9 @@ export default {
       },
       form27: {},
       form28: {},
+      form29: {
+        signPageUrl: ''
+      },
       storeProductScrmColumns:[],
       storeScrmColumns: [],
       photoArr: [],
@@ -2867,6 +2892,12 @@ export default {
     },
     getConfigByKey(key) {
       getConfigByKey(key).then(response => {
+        // 首次配置时 response.data 可能为 null(数据库中尚无该 configKey 记录)
+        if (!response.data) {
+          this.configId = null
+          this.configKey = key
+          return
+        }
         this.configId = response.data.configId
         this.configKey = response.data.configKey
         if (key == 'sys.oss.cloudStorage') {
@@ -3026,6 +3057,12 @@ export default {
         if (key == 'his.zzzs') {
           this.form28 = {...this.form28, ...JSON.parse(response.data.configValue)}
         }
+        if (key == 'his.doctorConfig') {
+          // 首次配置时 response.data 可能为 null,保留默认值即可
+          if (response.data && response.data.configValue) {
+            this.form29 = {...this.form29, ...JSON.parse(response.data.configValue)}
+          }
+        }
       })
     },
     /** 提交按钮 */
@@ -3303,6 +3340,34 @@ export default {
         }
       })
     },
+    submitForm29() {
+      this.$refs['form29'].validate(valid => {
+        if (valid) {
+          // configId 为空表示数据库中尚无该配置,走新增流程;否则走更新流程
+          if (this.configId) {
+            var param = { configId: this.configId, configKey: this.configKey, configValue: JSON.stringify(this.form29) }
+            updateConfigByKey(param).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess('修改成功')
+              }
+            })
+          } else {
+            var addParam = {
+              configName: '药师端配置',
+              configKey: 'his.doctorConfig',
+              configValue: JSON.stringify(this.form29),
+              configType: 'Y'
+            }
+            addConfig(addParam).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess('新增成功')
+                this.getConfigByKey('his.doctorConfig')
+              }
+            })
+          }
+        }
+      })
+    },
     formatColumns(){
       console.log(this.form27.pass_columns)