9797e46619f64bddb78d4b4eaa03d53069ac2ba4..6933522e3f1ffe8f8b1b7e169e2c54a7d24f0443
2025-09-02 zxl
Merge remote-tracking branch 'origin/dev' into dev
693352 对比 | 目录
2025-09-02 zxl
订单列表问题
12abc3 对比 | 目录
2025-09-02 peng
解决访问不稳定的问题
b8be56 对比 | 目录
2025-09-02 peng
操作栏固定右边
358edb 对比 | 目录
2025-09-02 peng
添加mq手动补偿机制
3cbaae 对比 | 目录
3个文件已修改
343 ■■■■ 已修改文件
manager/src/libs/axios.js 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/views/member/list/memberDetail.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/views/order/order/orderList.vue 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manager/src/libs/axios.js
@@ -21,15 +21,19 @@
const service = axios.create({
  timeout: 8000,
  baseURL: managerUrl,
  headers: {
    accessToken: getStore("accessToken")
  },
  baseURL: managerUrl
  // 移除固定的accessToken设置,改在请求拦截器中动态设置
});
var isRefreshToken = 0;
const refreshToken = getTokenDebounce();
service.interceptors.request.use(
  config => {
    // 动态设置accessToken
    const accessToken = getStore("accessToken");
    if (accessToken) {
      config.headers.accessToken = accessToken;
    }
    if (config.method == "get") {
      config.params = {
        _t: Date.parse(new Date()) / 1000,
@@ -101,24 +105,34 @@
      } else if (error.response.status === 404) {
        // 避免刷新token报错
      } else if (error.response.status === 403 || error.response.data.code === 20004) {
        isRefreshToken++;
        if (isRefreshToken === 1) {
        // 改进的token刷新机制,避免竞态条件
        if (isRefreshToken === 0) {
          isRefreshToken = 1;
          const getTokenRes = await refreshToken();
          if (getTokenRes === "success") {
            // 刷新token
            if (isRefreshToken === 1) {
              error.response.config.headers.accessToken = getStore(
                "accessToken"
              );
              return service(error.response.config);
            } else {
              router.go(0);
            }
            // 刷新token成功,重新发起请求
            error.response.config.headers.accessToken = getStore("accessToken");
            isRefreshToken = 0;
            return service(error.response.config);
          } else {
            // 刷新失败,跳转到登录页
            isRefreshToken = 0;
            Cookies.set("userInfoManager", "");
            setStore("accessToken", "");
            setStore("refreshToken", "");
            router.push("/login");
          }
          isRefreshToken = 0;
        } else {
          // 如果已经有请求在刷新token,等待一小段时间后重试
          await new Promise(resolve => setTimeout(resolve, 100));
          if (isRefreshToken === 0) {
            // token刷新完成,重新发起请求
            error.response.config.headers.accessToken = getStore("accessToken");
            return service(error.response.config);
          } else {
            // 超时或其他错误,跳转到登录页
            router.push("/login");
          }
        }
      } else {
        // 其他错误处理
@@ -133,46 +147,46 @@
// 原始的axios暴露出去用即可
export default service
// 防抖闭包来一波
// 改进的防抖机制
function getTokenDebounce() {
  let lock = false;
  let success = false;
  let resolveCallbacks = []; // 存储等待的回调
  return function() {
    if (!lock) {
      lock = true;
      let oldRefreshToken = getStore("refreshToken");
      handleRefreshToken(oldRefreshToken)
        .then(res => {
          if (res.success) {
            let { accessToken, refreshToken } = res.result;
            setStore("accessToken", accessToken);
            setStore("refreshToken", refreshToken);
            success = true;
            lock = false;
          } else {
            success = false;
            lock = false;
            router.push("/login");
          }
        })
        .catch(err => {
          success = false;
          lock = false;
        });
    }
    return new Promise(resolve => {
      // 一直看lock,直到请求失败或者成功
      const timer = setInterval(() => {
        if (!lock) {
          clearInterval(timer);
          if (success) {
            resolve("success");
          } else {
            resolve("fail");
          }
        }
      }, 500); // 轮询时间间隔
      if (!lock) {
        lock = true;
        resolveCallbacks = [resolve]; // 初始化回调数组
        let oldRefreshToken = getStore("refreshToken");
        handleRefreshToken(oldRefreshToken)
          .then(res => {
            if (res.success) {
              let { accessToken, refreshToken } = res.result;
              setStore("accessToken", accessToken);
              setStore("refreshToken", refreshToken);
              success = true;
            } else {
              success = false;
              router.push("/login");
            }
          })
          .catch(err => {
            console.error('Token刷新失败:', err);
            success = false;
          })
          .finally(() => {
            lock = false;
            // 同时解决所有等待的Promise
            const result = success ? "success" : "fail";
            resolveCallbacks.forEach(callback => callback(result));
            resolveCallbacks = [];
          });
      } else {
        // 如果正在刷新,将回调加入等待队列
        resolveCallbacks.push(resolve);
      }
    });
  };
}
manager/src/views/member/list/memberDetail.vue
@@ -1129,17 +1129,6 @@
        }
      },
    },
    watch: {
      $route (to, from) {
        // 正确打印路由对象的方法
        if(from.fullPath === "/member" && to.fullPath.includes("/orderList")){
          this.id = this.$route.query.id;
          this.init();
        }
      },
    },
    mounted() {
      this.id = this.$route.query.id;
      this.init();
manager/src/views/order/order/orderList.vue
@@ -102,7 +102,7 @@
          </div>
        </template>
        <template  slot="nickName" slot-scope="{ row }">
          <div style="width: 100%" @click="handleNickNameClick(row)">
          <div style="width: 100%;height:20px;min-width: 50px" @click="handleNickNameClick(row)">
            <span >{{row.nickName}}</span>
          </div>
@@ -124,17 +124,91 @@
          show-sizer
        ></Page>
      </Row>
      <!-- 修改模态框 -->
      <Modal v-model="descFlag" width="500">
        <!-- 自定义标题插槽 -->
        <div slot="header" :style="{ color: titleColor, fontSize: '16px', fontWeight: 'bold' }">
          {{ descTitle }}
        </div>
        <Form ref="form" :model="form" :rules="ruleValidate" :label-width="80">
          <Input v-model="form.id" v-show="false" />
          <FormItem label="头像">
            <img :src="form.face || defaultPic" class="face" />
            <Button type="text" class="upload" @click="() => {
            this.picModelFlag = true;
            this.$refs.ossManage.selectImage = true;
          }">修改</Button>
            <input type="file" style="display: none" id="file" />
          </FormItem>
          <FormItem label="用户名" prop="name">
            <Input v-model="form.username" style="width: 200px" disabled />
          </FormItem>
          <FormItem label="用户昵称" prop="name">
            <Input v-model="form.nickName" style="width: 200px" />
          </FormItem>
          <FormItem label="标签" prop="tags">
            <Select v-model="selectTagIds" multiple filterable :loading="selectLoading" style="width:200px;">
              <Option v-for="item in options" :key="item.id" :label="item.tagName" :value="item.id"></Option>
            </Select>
          </FormItem>
          <FormItem label="性别" prop="sex">
            <RadioGroup type="button" button-style="solid" v-model="form.sex">
              <Radio :label="1">
                <span>男</span>
              </Radio>
              <Radio :label="0">
                <span>女</span>
              </Radio>
            </RadioGroup>
          </FormItem>
          <!--        <FormItem label="修改密码" prop="password">-->
          <!--          <Input type="password" style="width: 220px" password v-model="form.newPassword" />-->
          <!--        </FormItem>-->
          <FormItem label="生日" prop="birthday">
            <DatePicker type="date" format="yyyy-MM-dd" v-model="form.birthday" style="width: 220px"></DatePicker>
          </FormItem>
          <FormItem label="所在地" prop="mail">
            {{ form.region || '暂无地址' }}
            <Button style="margin-left: 10px;" @click="$refs.map.open()">选择</Button>
          </FormItem>
        </Form>
        <div slot="footer">
          <Button @click="descFlag = false">取消</Button>
          <Button type="primary" @click="handleSubmitModal" :disabled="submitDisabled">
            确定
          </Button>
        </div>
      </Modal>
      <Modal width="1200px" v-model="picModelFlag">
        <ossManage @callback="callbackSelected" :isComponent="true" :initialize="picModelFlag" ref="ossManage" />
      </Modal>
      <multipleMap ref="map" @callback="selectedRegion" />
    </Card>
  </div>
</template>
<script>
import multipleMap from "@/components/map/multiple-map";
import ossManage from "@/views/sys/oss-manage/ossManage";
import * as API_Order from "@/api/order";
import JsonExcel from "vue-json-excel";
import * as API_Member from "@/api/member.js";
import { getTags } from "@/api/tag.js";
export default {
  name: "orderList",
  components: {
    "download-excel": JsonExcel,
    ossManage,
    multipleMap,
  },
  data() {
    return {
@@ -321,6 +395,7 @@
          key: "action",
          align: "center",
          width: 150,
          fixed:"right",
          render: (h, params) => {
            const buttons = [];
@@ -380,16 +455,128 @@
        {title: '已关闭', value: 'CANCELLED'},
      ],
      currentStatus: ''
      currentStatus: '',
      descTitle: "", // modal标题
      descFlag: false, //编辑查看框
      form:{},
      ruleValidate:{},
      selectTagIds: [],
      selectTags: [],
      defaultPic: require('@/assets/default.png'),
      selectLoading:false,
      picModelFlag: false, // 选择图片
      options: [],
      titleColor:'#333', // 默认标题颜色
      submitDisabled:false,
    };
  },
  methods: {
    handleNickNameClick(row){
      this.$options.filters.customRouterPush({ name: "member-detail", query: { id: row.memberId } })
    // 选中的图片
    callbackSelected(val) {
      console.log(val)
      this.picModelFlag = false;
      this.form.face = val.url;
    },
     handleNickNameClick(row){
      this.titleColor = '#333'
      this.form = {};
      this.selectTagIds = [];
      this.selectTags= [];
      this.descTitle ='';
      this.descFlag = true;
      this.getMemberInfo(row.memberId);
    },
    /**
     * 查询查看会员详情
     */
    async getMemberInfo(id) {
      this.submitDisabled = false;
      var that = this;
      await API_Member.getMemberInfoData(id).then((res) => {
        if (res.result) {
          debugger
          that.selectTags = []
          that.selectTagIds = []
          res.result.tags.forEach(element => {
            that.selectTags.push(element.tagName)
            that.selectTagIds.push(element.tagId)
          });
          this.descTitle = res.result.nickName;
          this.$set(this, "form", res.result);
        }else{
          //查询到用户不存在,或已删除情况
          this.descTitle = "用户已删除"
          this.titleColor ='#ff4d4f';
          this.submitDisabled = true;
        }
        console.log(this.form)
      });
    },
    remoteMethod(query) {
      this.selectLoading = true;
      var params = {
        "tagTypeKey": "USER",
        "pageNumber": 1,
        "pageSize": 500
      }
      getTags(params).then(res => {
        this.options = res.data;
        this.selectLoading = false;
      });
    },
    handleSubmitModal(){
      debugger
      const { nickName, sex, username, face, newPassword, id, regionId, region } = this.form;
      let time = new Date(this.form.birthday);
      let birthday = this.form.birthday === undefined ? '' :
        time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
      let submit = {
        regionId,
        region,
        nickName,
        username,
        sex,
        birthday,
        face,
        id
      };
      submit.tags = this.selectTagIds
      if (newPassword) {
        submit.password = this.md5(newPassword);
      }
      API_Member.updateMember(submit).then((res) => {
        if (res.result) {
          this.$Message.success("修改成功!");
          this.descFlag = false;
          this.getDataList();
        }
      });
    },
// 选中的地址
    selectedRegion(val) {
      if (val.type === 'select') {
        const paths = val.data.map(item => item.name).join(',')
        const ids = val.data.map(item => item.id).join(',')
        this.$set(this.form, 'region', paths)
        this.$set(this.form, 'regionId', ids)
      }
      else {
        this.$set(this.form, 'region', val.data.addr)
        this.$set(this.form, 'regionId', val.data.addrId)
      }
    },
    // 初始化数据
    init() {
      this.getDataList();
      this.remoteMethod("")
    },
    // 分页 改变页码
    changePage(v) {
@@ -425,7 +612,6 @@
          this.total = res.result.total;
        }
      });
      this.total = this.data.length;
      this.loading = false;
    },
    // 跳转详情页面
@@ -456,21 +642,22 @@
    deliverOrder(order) {
      console.log('------------->获取订单信息',order);
      console.log('订单sn编号',order.sn);
      // 这里可以调用相关的API进行发货操作
      // 调用发货API
      API_Order.sendMessage(order.sn).then((res) => {
        console.log('-------------->',res);
        if (res.success) {
          this.$Message.success('更新状态成功');
          this.getDataList(); // 刷新列表
          // 延迟5秒刷新列表mq消息是异步的无法实时同步需要执行延迟刷新
          setTimeout(() => {
            this.getDataList();
          }, 5000);
        } else {
          this.$Message.error('更新状态失败');
        }
      }).catch((error) => {
        console.error('更新状态失败:', error);
        this.$Message.error('更新状态失败,请重试');
      });
      // 暂时模拟操作
      this.$Message.success(`订单 ${order.sn} 更新状态成功!`);
      // 刷新列表数据
      this.getDataList();
    },
    // 导出订单
    async exportOrder() {
@@ -542,4 +729,9 @@
    background-color: #ffffff;
  }
}
.face {
  width: 60px;
  height: 60px;
  border-radius: 50%; // 圆形头像
}
</style>