浏览代码

Merge branch 'master' of https://gogs.zfire.top/yanwen/overseas-jsm-mall-front

FengChaoYu 2 天之前
父节点
当前提交
c86fcdd194

+ 3 - 0
.env.development

@@ -4,3 +4,6 @@ VITE_APP_BASE_API = 'https://jiasm.zfire.top/overseas-miniapp'
 
 VITE_APP_BASE_OSS = 'https://jiasm.zfire.top/overseas-api/img/get?key='
 
+# 高德地图秘钥
+VITE_AMAP_KEY = 'df9bfabcba60cc6e7ad45ae9923a682d'
+VITE_AMAP_SECURITY_JS_CODE = ''

+ 5 - 1
.env.production

@@ -1,5 +1,9 @@
-ENV = 'development'
+ENV = 'production'
 
 VITE_APP_BASE_API = 'https://jiasm.zfire.top/overseas-miniapp'
 
 VITE_APP_BASE_OSS = 'https://jiasm.zfire.top/overseas-api/img/get?key='
+
+# 高德地图秘钥
+VITE_AMAP_KEY = 'df9bfabcba60cc6e7ad45ae9923a682d'
+VITE_AMAP_SECURITY_JS_CODE = ''

+ 7 - 0
package-lock.json

@@ -8,6 +8,7 @@
       "name": "商城",
       "version": "0.0.0",
       "dependencies": {
+        "@amap/amap-jsapi-loader": "^1.0.1",
         "@ant-design/icons-vue": "^7.0.1",
         "ant-design-vue": "^4.2.6",
         "axios": "^1.13.2",
@@ -25,6 +26,12 @@
         "vite": "^7.2.4"
       }
     },
+    "node_modules/@amap/amap-jsapi-loader": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz",
+      "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==",
+      "license": "MIT"
+    },
     "node_modules/@ant-design/colors": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@ant-design/icons-vue": "^7.0.1",
     "ant-design-vue": "^4.2.6",
     "axios": "^1.13.2",

+ 1 - 2
src/api/goods.js

@@ -41,8 +41,7 @@ export const changeCart = params => request({
 export const removeCart = params => request({
   url: '/shpping/cart/remove',
   method: 'post',
-  data: params,
-  json: true
+  data: params
 })
 
 // 添加收藏

+ 7 - 0
src/api/order.js

@@ -63,3 +63,10 @@ export const getRefundDetail = params => request({
   method: 'get',
   params: params
 })
+
+// 获取订单数量
+export const getOrderCount = params => request({
+  url: '/order/count',
+  method: 'get',
+  params: params
+})

+ 21 - 0
src/api/user.js

@@ -99,3 +99,24 @@ export const queryRegion = params => request({
   method: 'post',
   params: params
 })
+
+// 我的账单
+export const getCreditBillList = params => request({
+  url: '/user/company/credit/bill/list',
+  method: 'post',
+  data: params
+})
+
+// 获取我某一条账单的明细
+export const getCreditBillDetail = params => request({
+  url: '/user/company/credit/acc/list',
+  method: 'post',
+  data: params
+})
+
+// 获取用户授信记录
+export const getCreditRecordList = (params = {}) => request({
+  url: '/user/company/credit/record/list',
+  method: 'post',
+  data: params
+})

二进制
src/assets/images/poi-marker-red.png


+ 0 - 9
src/components/card/index.vue

@@ -1,9 +0,0 @@
-<template>
-  <a-card
-    v-bind="$attrs"
-    :headStyle="{padding: '0 15px', minHeight: '50px'}"
-    :bodyStyle="{padding: '15px'}"
-  >
-    <slot />
-  </a-card>
-</template>

+ 168 - 0
src/pages/branch/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="branch-box">
+    <a-card title="分支查找器">
+      <div class="map-box">
+        <div class="map-search">
+          <a-space direction="vertical" size="large">
+            <div class="title">搜索以查找商家的地址、电话号码等信息</div>
+            <a-input-search
+              v-model:value="mainData.name"
+              placeholder="点击输入商家名称搜索"
+              allowClear
+              :disabled="mainData.disabled"
+              @search="handleSearch"
+            />
+            <a-button type="primary" :disabled="mainData.disabled" block @click="handleSearch">立即搜索</a-button>
+          </a-space>
+        </div>
+        <div class="map-content">
+          <div id="container"></div>
+        </div>
+      </div>
+    </a-card>
+  </div>
+</template>
+
+<script setup lang="js">
+import { message } from 'ant-design-vue';
+import { reactive, onMounted, onUnmounted, computed } from 'vue';
+import { useStorageStore } from '@/store/storage';
+import AMapLoader from '@amap/amap-jsapi-loader';
+import MarkIcon from '@/assets/images/poi-marker-red.png';
+
+const storageStore = useStorageStore();
+
+let map = null;
+let aMap = null;
+let infoWindow = null;
+
+const mainData = reactive({
+  name: '',
+  disabled: false,
+  markerData: {}
+})
+
+const storeList = computed(() => storageStore.list);
+
+const initMap = (center) => {
+  return new Promise((resolve, reject) => {
+      window._AMapSecurityConfig = {
+      securityJsCode: ''
+    };
+    AMapLoader.load({
+      key: import.meta.env.VITE_AMAP_KEY,
+      version: '2.0',
+      plugins: [
+        'AMap.Scale',
+        'AMap.Marker',
+        'AMap.InfoWindow',
+        'AMap.Pixel',
+        'AMap.Size'
+    ],
+    }).then((AMap) => {
+      map = new AMap.Map('container', {
+        zoom: 13,
+        viewMode: '3D',
+        center: center,
+        mapStyle: "amap://styles/whitesmoke",
+      });
+      infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -30)})
+      resolve(AMap)
+    }).catch(() => {
+      reject('高德地图初始化失败')
+    })
+  })
+}
+
+const addMapMark = (AMap, markerList) => {
+  markerList.forEach((item, index) => {
+    const markerIcon = new AMap.Icon({
+      size: new AMap.Size(25, 34),
+      image: MarkIcon,
+      imageSize: new AMap.Size(25, 40),
+    });
+    mainData.markerData[index] = new AMap.Marker({
+      position: [Number(item.lng), Number(item.lat)],
+      title: item.storageName,
+      icon: markerIcon,
+      offset: new AMap.Pixel(-13, -30),
+      map: map
+    });
+    mainData.markerData[index].content = `
+      <div>商家: ${item.companyName || ''}</div>
+      <div>电话: ${item.storageMobile || ''}</div>
+      <div>仓库: ${item.storageName || ''}</div>
+      <div>地址: ${item.storageAddress || ''}</div>
+    `;
+    mainData.markerData[index].on('click', e => {
+      infoWindow?.setContent(e.target.content);
+      infoWindow?.open(map, e.target.getPosition());
+    })
+    map?.add(mainData.markerData[index]);
+  })
+}
+
+const removeMapMark = () => {
+  Object.keys(mainData.markerData).forEach(key => {
+    mainData.markerData[key]?.setMap(null)
+    mainData.markerData[key] = null;
+  })
+}
+
+const handleSearch = () => {
+  mainData.disabled = true;
+  const timer = setTimeout(() => {
+    mainData.disabled = false;
+    clearTimeout(timer);
+  }, 1000);
+  const validList = storeList.value.filter(item => item.companyName.indexOf(mainData.name) > -1);
+  if (validList.length > 0) {
+    removeMapMark();
+    addMapMark(aMap, validList);
+    map?.setCenter([Number(validList[0].lng), Number(validList[0].lat)]);
+  } else {
+    message.info(`未找到名为${mainData.name}的商家信息`);
+  }
+}
+
+onMounted(async () => {
+  if (storeList.value.length === 0) {
+    await storageStore.fetchListData();
+  };
+  const target = storeList.value.find(item => item.storageId === storageStore.activedId);
+  const center = [Number(target?.lng || 113.26), Number(target?.lat || 23.13)];
+  const AMap = await initMap(center);
+  aMap = AMap;
+  addMapMark(AMap, storeList.value);
+});
+
+onUnmounted(() => {
+  map?.destroy();
+  aMap = null;
+});
+
+</script>
+
+<style lang="less" scoped>
+.branch-box {
+  padding: 20px;
+  background: #fff;
+  .map-box {
+    width: 100%;
+    display: flex;
+    .map-search {
+      width: 300px;
+      flex-shrink: 0;
+      margin-right: 20px;
+    }
+    .map-content {
+      flex: 1;
+      min-width: 1px;
+      #container {
+        width: 100%;
+        height: 600px;
+      }
+    }
+  }
+}
+</style>

+ 4 - 1
src/pages/home/components/AddressList.vue

@@ -7,7 +7,10 @@
         size="small"
         style="margin-bottom: 10px;"
       >
-        <template #title>{{item.province}} {{item.city}} {{item.area}} {{item.street}}</template>
+        <template #title>
+          <a-tag v-if="item.defaultAddr" color="processing">默认</a-tag>
+          <span>{{item.province}} {{item.city}} {{item.area}} {{item.street}}</span>
+        </template>
         <template #extra>
           <a-button v-if="showSelect" size="small" type="link" @click="handleSelect(item)">选择</a-button>
           <a-button v-if="showEdit" size="small" type="link" @click="handleEdit(item)">编辑</a-button>

+ 3 - 3
src/pages/home/components/AddressModal.vue

@@ -58,7 +58,7 @@ const HandleTypeEnum = {
   UPDATE: 'update'
 }
 const mainData = reactive({
-  stepNum: 0,
+  stepNum: -1,
   addressId: '',
   handleType: HandleTypeEnum.ADD
 })
@@ -88,8 +88,8 @@ const handleFn = () => {
 
 watch(() => props.open, newVal => {
   if (newVal) {
-    mainData.stepNum = 0
-    mainData.addressId = ''
+    mainData.stepNum = -1;
+    mainData.addressId = '';
     nextTick(() => {
       mainData.stepNum = 1
     })

+ 4 - 4
src/pages/home/components/CartList.vue

@@ -12,7 +12,7 @@
       </div>
       <div class="list-box">
         <div v-for="(item, index) in mainData.goodsList" :key="index" class="list-item">
-          <div v-if="item.stockQty > 0" class="check-box" @click="toggleCheck(index)">
+          <div v-if="item.stockQty > 0 || mainData.isEdit" class="check-box" @click="toggleCheck(index)">
             <CheckCircleOutlined class="check-item" :class="item.selected ? 'actived' : ''" />
           </div>
           <div class="goods-image">
@@ -141,7 +141,7 @@ const calcTotalPrice = () => {
   // 统计总金额
   for (let i = 0; i < goodsList.length; i++) {
     // 判断选中才会计算价格
-    if (goodsList[i].selected) {
+    if (goodsList[i].selected && goodsList[i].stockQty > 0) {
       // 所有价格加起来
       total_price += goodsList[i].num * goodsList[i].price
       total_num += Number(goodsList[i].num)
@@ -282,7 +282,7 @@ const handleDelete = () => {
       content: '确定要删除选中的商品吗?',
       onOk() {
         removeCart({
-          shoppingCartIds: delIds,
+          shoppingCartIds: delIds.join(','),
           userId: props.userId,
           storageId: props.storageId
         }).then(() => {
@@ -295,7 +295,7 @@ const handleDelete = () => {
 }
 
 const handleNext = () => {
-  const selectedList = mainData.goodsList.filter(item => item.selected).map(child => {
+  const selectedList = mainData.goodsList.filter(item => item.selected && item.stockQty > 0).map(child => {
     return {
       goodsId: child.goodsId,
       goodsSpecId: child.goodsSpecId,

+ 1 - 1
src/pages/home/components/CartModal.vue

@@ -53,7 +53,7 @@ const props = defineProps({
 const emits = defineEmits(['close']);
 
 const mainData = reactive({
-  stepNum: 1,
+  stepNum: -1,
   buyGoods: [],
   orderId: ''
 })

+ 0 - 1
src/pages/home/components/Category.vue

@@ -29,7 +29,6 @@ const props = defineProps({
 const emits = defineEmits(['change'])
 
 const changeTab = (index) => {
-  console.log(index)
   emits('change', index)
 }
 

+ 317 - 0
src/pages/home/components/CreditBillList.vue

@@ -0,0 +1,317 @@
+<template>
+  <div class="credit-box">
+    <div class="tabs-box">
+      <a-tabs v-model:activeKey="mainData.tabValue" @change="handleChangeTab">
+        <a-tab-pane
+          v-for="item in mainData.tabs"
+          :key="item.value"
+          :tab="item.name"
+          :disabled="mainData.disabled"
+        />
+        <template #rightExtra>
+          <a-select
+            v-model:value="mainData.year"
+            style="width: 120px"
+            :options="mainData.yearOptions"
+            @change="handleChangeYear"
+          />
+        </template>
+      </a-tabs>
+    </div>
+    <div class="card-list__box">
+      <a-empty v-if="mainData.list.length === 0" description="我的账单为空" />
+      <template v-else>
+        <a-card
+          v-for="(item, index) in mainData.list"
+          :key="index"
+          size="small"
+          class="card-item"
+          @click="handleDetail(item, index)"
+        >
+          <div class="card-item__main-content">
+            <div class="handle-icon__box">
+              <DownOutlined v-if="item.showChildren" style="color: #999; font-size: 28px;" />
+              <RightOutlined v-else style="color: #999; font-size: 28px;" />
+            </div>
+            <div class="main-row__content">
+              <div class="card-item__content">
+                <div class="item-label">企业名称</div>
+                <div class="item-value">{{ item.companyWechatName }}</div>
+              </div>
+              <div class="card-item__content">
+                <div class="item-label">账单总金额</div>
+                <div class="item-value">{{ twoFloatNum(item.amount) }}</div>
+              </div>
+              <div class="card-item__content">
+                <div class="item-label">剩余未还金额</div>
+                <div class="item-value">{{ twoFloatNum(item.remainingAmount) }}</div>
+              </div>
+              <div class="card-item__content">
+                <div class="item-label">账单创建日期</div>
+                <div class="item-value">{{ item.createDate }}</div>
+              </div>
+              <div class="card-item__content">
+                <div class="item-label">账单到期日期</div>
+                <div class="item-value">{{ item.dueDate }}</div>
+              </div>
+              <div class="card-item__content">
+                <div class="item-label">是否已还款</div>
+                <div class="item-value">{{ getStatusText(item.status) }}</div>
+              </div>
+              <div v-if="item.status === 'REPAID'" class="card-item__content">
+                <div class="item-label">还款时间</div>
+                <div class="item-value">{{ item.updateTime }}</div>
+              </div>
+            </div>
+          </div>
+          <div v-if="item.showChildren" class="child-main__content">
+            <a-empty v-if="!item.children || item.children.length === 0" description="账单明细为空" />
+            <template v-else>
+              <a-card
+                v-for="(child, index) in item.children"
+                :key="index"
+                size="small"
+                class="child-card__item"
+              >
+                <div class="child-card__content">
+                  <div class="item-label">企业名称</div>
+                  <div class="item-value">{{ child.companyWechatName }}</div>
+                </div>
+                <a-row :gutter="12">
+                  <a-col :span="12">
+                    <div class="child-card__content">
+                      <div class="item-label">交易类型</div>
+                      <div class="item-value">{{ getTypeText(child.transactionType) }}</div>
+                    </div>
+                  </a-col>
+                  <a-col :span="12">
+                    <div class="child-card__content">
+                      <div class="item-label">交易金额</div>
+                      <div class="item-value">{{ twoFloatNum(child.amount) }}</div>
+                    </div>
+                  </a-col>
+                </a-row>
+                <a-row :gutter="12">
+                  <a-col :span="24">
+                    <div class="child-card__content">
+                      <div class="item-label">订单ID</div>
+                      <div class="item-value">{{ child.orderId }}</div>
+                    </div>
+                  </a-col>
+                </a-row>
+                <a-row :gutter="12">
+                  <a-col :span="24">
+                    <div class="child-card__content">
+                      <div class="item-label">交易时间</div>
+                      <div class="item-value">{{ child.createTime }}</div>
+                    </div>
+                  </a-col>
+                </a-row>
+              </a-card>
+            </template>
+          </div>
+        </a-card>
+      </template>
+      <a-flex v-if="mainData.total > 0 && mainData.total > mainData.list.length" align="center" justify="center">
+        <a-button type="link" @click="handleLoadMore">点击加载更多</a-button>
+      </a-flex>
+    </div>
+  </div>
+</template>
+
+<script setup lang="js">
+import { DownOutlined, RightOutlined } from '@ant-design/icons-vue';
+import dayjs from 'dayjs';
+import { reactive, onMounted } from 'vue';
+import { getCreditBillList, getCreditBillDetail } from '@/api/user';
+import { useStorageStore } from '@/store/storage';
+import { twoFloatNum } from '@/utils/index';
+
+const storageStore = useStorageStore();
+const mainData = reactive({
+  tabValue: '',
+  tabs: [
+    {
+      name: '未还款',
+      value: 'UNPAID'
+    },
+    {
+      name: '部分还款',
+      value: 'REPAYMENT'
+    },
+    {
+      name: '已还款',
+      value: 'REPAID'
+    }
+  ],
+  yearOptions: [],
+  year: '',
+  companyWechatId: '',
+  list: [],
+  pageNum: 1,
+  total: 0,
+  disabled: false
+})
+
+const getYearOptions = () => {
+  const curYear = dayjs().format('YYYY');
+  mainData.yearOptions = [
+    {
+      label: `${curYear}年`,
+      value: `${curYear}`
+    },
+    {
+      label: `${curYear - 1}年`,
+      value: `${curYear - 1}`
+    },
+    {
+      label: `${curYear - 2}年`,
+      value: `${curYear - 2}`
+    }
+  ]
+}
+
+const fetchCreditBillList = () => {
+  const status = mainData.tabs.find(item => item.value === mainData.tabValue)?.value || '';
+  getCreditBillList({
+    status: status,
+    year: mainData.year,
+    companyWechatId: mainData.companyWechatId,
+    pageNum: mainData.pageNum,
+    pageSize: 10
+  }).then(res => {
+    mainData.list = mainData.list.concat(res.data?.records || []);
+    mainData.total = res.data?.total || 0;
+  }).finally(() => {
+    mainData.disabled = false;
+  })
+}
+
+const handleDetail = (row, index) => {
+  if (row.completed) {
+    mainData.list[index].showChildren = row.showChildren ? false : true;
+  } else {
+    if (mainData.list[index].loading) return;
+    mainData.list[index].loading = true;
+    getCreditBillDetail({
+      billId: row.billId,
+      companyWechatId: mainData.companyWechatId,
+      pageNum: 1,
+      pageSize: -1
+    }).then(res => {
+      mainData.list[index].completed = true;
+      mainData.list[index].showChildren = true;
+      mainData.list[index].children = res.data?.records || [];
+    }).finally(() => {
+      mainData.list[index].loading = false;
+    })
+  }
+}
+
+const handleChangeTab = tabValue => {
+  mainData.tabValue = tabValue;
+  mainData.list = [];
+  mainData.pageNum = 1;
+  mainData.total = 0;
+  mainData.disabled = true;
+  fetchCreditBillList();
+}
+
+const handleChangeYear = () => {
+  mainData.list = [];
+  mainData.pageNum = 1;
+  mainData.total = 0;
+  mainData.disabled = true;
+  fetchCreditBillList();
+}
+
+const handleLoadMore = () => {
+  mainData.pageNum += 1;
+  mainData.disabled = true;
+  fetchCreditBillList();
+}
+
+const getStatusText = status => {
+  return mainData.tabs.find(item => item.value === status)?.name || '';
+}
+
+const getTypeText = (type) => {
+  if (type === 'CONSUMPTION') return '消费'
+  if (type === 'REPAYMENT') return '还款'
+  if (type === 'REFUND') return '退款'
+  return ''
+}
+
+onMounted(() => {
+  getYearOptions();
+  mainData.tabValue = mainData.tabs[0].value;
+  mainData.companyWechatId = storageStore.list.find(item => item.storageId === storageStore.activedId)?.companyWechatId || '';
+  mainData.year = mainData.yearOptions[0].value;
+  fetchCreditBillList();
+})
+</script>
+
+<style lang="less" scoped>
+.card-list__box {
+  .card-item {
+    margin-bottom: 15px; 
+    .card-item__main-content {
+      box-sizing: border-box;
+      width: 100%;;
+      display: flex;
+      align-items: center;
+      .handle-icon__box {
+        flex-shrink: 0;
+        width: 40px;
+        cursor: pointer;
+      }
+      .card-item__content {
+        flex: 1;
+        display: flex;
+        padding: 5px 0;
+        align-items: flex-start;
+        .item-label {
+          flex: 0 0 100px;
+          flex-shrink: 0;
+          color: #666;
+          font-size: 14px;
+        }
+        .item-value {
+          flex: 1;
+          min-width: 0;
+          color: #222;
+          font-size: 14px;
+        }
+      }
+    }
+    .child-main__content {
+      padding: 10px;
+      .child-card__item {
+        margin-bottom: 10px;
+        border-radius: 8px;
+        &:last-of-type {
+          margin-bottom: 0;
+        }
+        .child-card__content {
+          width: 100%;
+          display: flex;
+          padding: 6px 0;
+          align-items: flex-start;
+          .item-label {
+            flex: 0 0 70px;
+            flex-shrink: 0;
+            color: #666;
+            font-size: 14px;
+          }
+          .item-value {
+            flex: 1;
+            min-width: 0;
+            color: #222;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 40 - 0
src/pages/home/components/CreditModal.vue

@@ -0,0 +1,40 @@
+<template>
+  <a-drawer
+    :open="open"
+    title="我的账单"
+    width="600px"
+    placement="right"
+    @close="emits('close')"
+  >
+    <div class="drawer-box">
+      <CreditBillList v-if="mainData.stepNum == 1" />
+    </div>
+  </a-drawer>
+</template>
+
+<script setup lang="js">
+import CreditBillList from './CreditBillList.vue';
+
+import { reactive, watch, nextTick } from 'vue';
+const props = defineProps({
+  open: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['close']);
+
+const mainData = reactive({
+  stepNum: -1
+})
+
+watch(() => props.open, newVal => {
+  if (newVal) {
+    mainData.stepNum = -1;
+    nextTick(() => {
+      mainData.stepNum = 1;
+    })
+  }
+})
+</script>

+ 89 - 0
src/pages/home/components/CreditRecordList.vue

@@ -0,0 +1,89 @@
+<template>
+  <div class="card-list__box">
+    <a-empty v-if="mainData.list.length === 0" description="授信记录为空" />
+    <template v-else>
+      <a-card
+        v-for="(item, index) in mainData.list"
+        :key="index"
+        size="small"
+        class="card-item"
+      >
+        <div class="card-item__content">
+          <div class="item-label">企业名称</div>
+          <div class="item-value">{{ item.companyWechatName }}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">授信功能</div>
+          <div class="item-value">{{item.isCreditEnabled ? '开启' : '关闭'}}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">授信额度</div>
+          <div class="item-value">{{ twoFloatNum(item.creditLimit) }}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">可用额度</div>
+          <div class="item-value">{{ twoFloatNum(item.availableCredit) }}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">授信时间</div>
+          <div class="item-value">{{ item.createTime }}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">账单日</div>
+          <div class="item-value">{{ item.billingDay }}</div>
+        </div>
+        <div class="card-item__content">
+          <div class="item-label">账期天数</div>
+          <div class="item-value">{{ item.paymentGracePeriod }}</div>
+        </div>
+      </a-card>
+    </template>
+  </div>
+</template>
+
+<script setup lang="js">
+import { onMounted, reactive } from 'vue';
+import { twoFloatNum } from '@/utils/index';
+import { getCreditRecordList } from '@/api/user';
+
+const mainData = reactive({
+  list: []
+})
+
+const fetchCreditRecordList = async () => {
+  const res = await getCreditRecordList({});
+  mainData.list = res.data || [];
+}
+
+onMounted(() => {
+  fetchCreditRecordList();
+})
+
+</script>
+
+<style lang="less" scoped>
+.card-list__box {
+  .card-item {
+    width: 100%;
+    margin-bottom: 15px;
+    .card-item__content {
+      width: 100%;
+      display: flex;
+      padding: 3px 0;
+      align-items: flex-start;
+      .item-label {
+        flex: 0 0 70px;
+        flex-shrink: 0;
+        color: #666;
+        font-size: 14px;
+      }
+      .item-value {
+        flex: 1;
+        min-width: 0;
+        color: #222;
+        font-size: 14px;
+      }
+    }
+  }
+}
+</style>

+ 40 - 0
src/pages/home/components/CreditRecordModal.vue

@@ -0,0 +1,40 @@
+<template>
+  <a-drawer
+    :open="open"
+    title="授信记录"
+    width="600px"
+    placement="right"
+    @close="emits('close')"
+  >
+    <div class="drawer-box">
+      <CreditRecordList v-if="mainData.stepNum == 1" />
+    </div>
+  </a-drawer>
+</template>
+
+<script setup lang="js">
+import CreditRecordList from './CreditRecordList.vue';
+import { reactive, watch, nextTick } from 'vue';
+
+const props = defineProps({
+  open: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emits = defineEmits(['close']);
+
+const mainData = reactive({
+  stepNum: -1
+})
+
+watch(() => props.open, newVal => {
+  if (newVal) {
+    mainData.stepNum = -1;
+    nextTick(() => {
+      mainData.stepNum = 1;
+    })
+  }
+})
+</script>

+ 19 - 9
src/pages/home/components/OrderList.vue

@@ -120,26 +120,26 @@
             <!-- <div class="btn-group" v-if="item.orderStatus == 'OVER' && tabCurrent != 'REFUND'">
             </div> -->
             <!-- 按钮:超时未支付 -->
-            <div class="btn-group" v-if="item.orderStatus == 'TIMEOUT'">
+            <!-- <div class="btn-group" v-if="item.orderStatus == 'TIMEOUT'">
               <div class="button gray">查看订单</div>
-            </div>
+            </div> -->
             <!-- 按钮:售后中 -->
-            <div class="btn-group" v-if="item.orderStatus == 'REFUND'">
+            <!-- <div class="btn-group" v-if="item.orderStatus == 'REFUND'">
               <div class="button gray">查看订单</div>
-            </div>
+            </div> -->
             <!-- 按钮:售后中 待商家处理 -->
-            <div class="btn-group" v-if="item.orderStatus == 'DSJCL'">
+            <!-- <div class="btn-group" v-if="item.orderStatus == 'DSJCL'">
               <div class="button gray" @click="toReturnDetail(item.orderRefundId)">售后详情</div>
-            </div>
+            </div> -->
             <!-- 按钮:售后中 待买家处理 -->
-            <div class="btn-group" v-if="item.orderStatus == 'DMJCL'">
+            <!-- <div class="btn-group" v-if="item.orderStatus == 'DMJCL'">
               <div class="button red" @click="toReturnDetail(item.orderRefundId)" v-if="item.examineStatus == 'OK'">
                 提交资料
               </div>
               <div class="button white" @click="toApplyReturn(item.orderId)" v-if="item.examineStatus == 'FAIL'">
                 重新申请
               </div>
-            </div>
+            </div> -->
           </div>
         </div>
       </div>
@@ -164,13 +164,19 @@ import {
 } from '@/api/order';
 import { useUserStore } from '@/store/user';
 
+const props = defineProps({
+  type: {
+    type: String,
+    default: ''
+  }
+})
+
 const emits = defineEmits(['view-detail']);
 
 const userStore = useUserStore();
 
 const tabCurrent = computed(() => mainData.tabs[mainData.tabIndex].key);
 
-
 const mainData = reactive({
   tabs: [
     { key: '', name: '全部' },
@@ -329,6 +335,10 @@ const formatPriceText = price => {
 }
 
 onMounted(() => {
+  mainData.tabIndex = mainData.tabs.findIndex(item => item.key === props.type);
+  if (mainData.tabIndex < 0) {
+    mainData.tabIndex = 0;
+  }
   fetchOrderList()
 })
 

+ 8 - 3
src/pages/home/components/OrderModal.vue

@@ -3,6 +3,7 @@
     <div class="drawer-box">
       <OrderList
         v-if="mainData.stepNum == 1"
+        :type="type"
         @view-detail="viewDetail"
       />
       <OrderDetail
@@ -21,17 +22,21 @@ import OrderList from './OrderList.vue';
 import OrderDetail from './OrderDetail.vue';
 import { reactive, watch, nextTick } from 'vue';
 
-defineProps({
+const props = defineProps({
   open: {
     type: Boolean,
     default: false
+  },
+  type: {
+    type: String,
+    default: ''
   }
 })
 
 const emits = defineEmits(['close']);
 
 const mainData = reactive({
-  stepNum: 1,
+  stepNum: -1,
   orderId: '',
   orderRefundId: ''
 })
@@ -46,7 +51,7 @@ const handlePrevStep = () => {
   mainData.stepNum -= 1;
 }
 
-watch(() => mainData.open, newVal => {
+watch(() => props.open, newVal => {
   if (newVal) {
     mainData.stepNum = -1;
     nextTick(() => {

+ 35 - 25
src/pages/home/components/SubmitCart.vue

@@ -99,7 +99,7 @@
             style="width: 100%;"
             v-model:value="mainData.pickTime"
             :options="mainData.pickTimeOptions"
-            placeholder="请选择选择提货时间"
+            placeholder="请选择提货时间"
             allowClear
           />
         </a-form-item>
@@ -244,7 +244,8 @@ const mainData = reactive({
   isFullPieceGoods: false,
   disabled: false,
   addressOpen: false,
-  openAddressPanel: false
+  openAddressPanel: false,
+  allTimeList: []
 })
 
 const storageName = computed(() => {
@@ -284,25 +285,31 @@ const fetchPickTimeList = async () => {
     storageId: props.storageId
   })
   const list = res.data || [];
-  const validList = list.filter(item => item.overCurTime);
-  const dateList = validList.length > 0 ? validList[0].countList : [];
-  dateList.forEach((item, index) => {
+  mainData.allTimeList = list;
+  if (list.length === 0) return;
+  const dateList = list[0].countList.map(item => item.date);
+  dateList.forEach((pickDate, index) => {
     mainData.pickTimeOptions[index] = {};
-    list.forEach(child => {
-      if (item.count < child.limitNum) {
-        mainData.pickTimeOptions[index].value = item.date;
-        mainData.pickTimeOptions[index].label = item.date;
-        if (mainData.pickTimeOptions[index].children === undefined) {
-          mainData.pickTimeOptions[index].children = []
-        }
-        mainData.pickTimeOptions[index].children.push({
-          value: `${child.startTime}-${child.endTime}`,
-          label: `${child.startTime}-${child.endTime}`
-        })
+    mainData.pickTimeOptions[index].value = pickDate;
+    mainData.pickTimeOptions[index].label = pickDate;
+    mainData.pickTimeOptions[index].children = [];
+    for (let data  of list) {
+      // 过滤掉当前时间已超时间的
+      if (index == 0 && !data.overCurTime) {
+        continue
       }
-    })
+      // 过滤掉已超限制订单量的
+      const dataItem = data.countList.find(item => item.date === pickDate)
+      if (data.limitNum > 0 && dataItem.count >= data.limitNum) {
+        continue
+      }
+      mainData.pickTimeOptions[index].children.push({
+        value: `${data.startTime}-${data.endTime}`,
+        label: `${data.startTime}-${data.endTime}`
+      })
+    }
   })
-  mainData.pickTimeOptions = mainData.pickTimeOptions.filter(item => item.children && item.children.length > 0)
+  mainData.pickTimeOptions = mainData.pickTimeOptions.filter(item => item.children && item.children.length > 0);
 }
 
 const fetchOrderInfo = async () => {
@@ -368,13 +375,16 @@ const getSendTime = () => {
     const amEnd = mainData.dispatchTime[1] === 'AM' ? ' 12:00:00' : ' 23:59:59';
     return {
       startTime: mainData.dispatchTime[0] + amStart,
-      endTime: mainData.dispatchTime[0] + amEnd
+      endTime: mainData.dispatchTime[0] + amEnd,
+      pickTimeId: ''
     }
   } else {
     const hourMinArr = (mainData.pickTime[1] || '').split('-');
+    const target = mainData.allTimeList.find(item => item.startTime === hourMinArr[0] && item.endTime === hourMinArr[1])
     return {
       startTime: mainData.pickTime[0] + ` ${hourMinArr[0]}:00`,
-      endTime: mainData.pickTime[0] + ` ${hourMinArr[1]}:00`
+      endTime: mainData.pickTime[0] + ` ${hourMinArr[1]}:00`,
+      pickTimeId: target?.id || ''
     }
   }
 }
@@ -390,12 +400,10 @@ const handleSubmit = () => {
       buyerMsg: mainData.remark,
       userCouponId: '',
       payTypeId: ['','ONLINE','STORE','CREDIT'][mainData.payType],
-      pickTimeId: '',
+      pickTimeId: obj.pickTimeId,
       appointmentPickStartTime: obj.startTime,
-      appointmentPickEndTime: obj.endTime
-    }
-    if (mainData.takeGoodsType === TakeTypeEnum.DISPATCH) {
-      params.userAddressId = mainData.userAddressId || ''
+      appointmentPickEndTime: obj.endTime,
+      userAddressId: mainData.userAddressId || ''
     }
     mainData.disabled = false
     orderBuy(params).then(res => {
@@ -405,9 +413,11 @@ const handleSubmit = () => {
       if (res.data.isPay === false) {
         if (mainData.payType === PayTypeEnum.STORE) {
           message.success('购买成功,待商家确认')
+          userStore.fetchUserDetail()
           emits('next-step', res.data.id)
         } else if (mainData.payType === PayTypeEnum.CREDIT) {
           message.success('购买成功')
+          userStore.fetchUserDetail()
           emits('next-step', res.data.id)
         }
       }

+ 9 - 3
src/pages/home/components/Toolbar.vue

@@ -2,8 +2,8 @@
   <div class="headeer-toolbar">
     <a-flex align="center" justify="flex-end">
       <div class="toolbar-item" @click="viewOrder">我的订单</div>
-      <div class="toolbar-item">分支</div>
-      <div class="toolbar-item" title="点击查看培训信息" @click="handleTrain">培训</div>
+      <div class="toolbar-item" @click="handleBranch">分支</div>
+      <div class="toolbar-item" @click="handleTrain">培训</div>
       <div class="toolbar-item" @click="handleContact">联系我们 </div>
       <a-dropdown placement="bottom">
         <div class="toolbar-item">语言切换</div>
@@ -68,12 +68,18 @@ const handleLanguage = language => {
   }, 250)
 }
 
-const handleTrain = async () => {
+const handleTrain = () => {
   router.push({
     path: '/train'
   })
 }
 
+const handleBranch = () => {
+  router.push({
+    path: '/branch'
+  })
+}
+
 const handleContact = () => {
   emits('contact-us')
 }

+ 119 - 16
src/pages/home/components/UserModal.vue

@@ -2,39 +2,142 @@
   <a-drawer
     :open="open"
     title="用户信息"
-    width="520px"
+    width="600px"
     placement="right"
     @close="emits('close')"
   >
     <div class="drawer-box">
-      <div class="row-item__content">
-        <div class="row-item__label">昵称: </div>
-        <div class="row-item__value">{{ userStore.userDetail.nickName }}</div>
-      </div>
-      <div class="row-item__content">
-        <div class="row-item__label">手机号: </div>
-        <div class="row-item__value">{{ userStore.userDetail.mobile }}</div>
-      </div>
-      <div class="row-item__content">
-        <div class="row-item__label">邮箱: </div>
-        <div class="row-item__value">{{ userStore.userDetail.email }}</div>
-      </div>
+      <a-card size="small" title="用户信息" style="margin-bottom: 15px;">
+        <div class="row-item__content">
+          <div class="row-item__label">昵称: </div>
+          <div class="row-item__value">{{ userStore.userDetail.nickName }}</div>
+        </div>
+        <div class="row-item__content">
+          <div class="row-item__label">手机号: </div>
+          <div class="row-item__value">{{ userStore.userDetail.mobile }}</div>
+        </div>
+        <div class="row-item__content">
+          <div class="row-item__label">邮箱: </div>
+          <div class="row-item__value">{{ userStore.userDetail.email }}</div>
+        </div>
+      </a-card>
+      <a-card size="small" title="我的订单" style="margin-bottom: 15px;">
+        <template #extra>
+          <a-button type="link" @click="openOrderModal('')">全部订单</a-button>
+        </template>
+        <a-card-grid
+          v-for="item in mainData.orderList"
+          :key="item.key"
+          :hoverable="false"
+          style="width: 33.3333%; text-align: center"
+          @click="openOrderModal(item.key)"
+        >
+          <a-badge :count="item.num">
+            <a-button type="text">{{ item.name }}</a-button>
+          </a-badge>
+        </a-card-grid>
+      </a-card>
+      <a-card size="small" title="更多服务" style="margin-bottom: 15px;">
+        <a-card-grid
+          v-for="item in mainData.moreMenusList"
+          :key="item.name"
+          :hoverable="false"
+          style="width: 25%; text-align: center"
+          @click="handleMoreMenus(item)"
+        >
+          <a-button type="text">{{ item.name }}</a-button>
+        </a-card-grid>
+      </a-card>
     </div>
+    <OrderModal
+      :open="mainData.openOrder"
+      :type="mainData.orderType"
+      @close="mainData.openOrder = false"
+    />
+    <AddressModal
+      :open="mainData.openAddress"
+      @close="mainData.openAddress = false"
+    />
+    <CreditModal
+      :open="mainData.openCredit"
+      @close="mainData.openCredit = false"
+    />
+    <CreditRecordModal
+      :open="mainData.openRecord"
+      @close="mainData.openRecord = false"
+    />
   </a-drawer>
 </template>
 
 <script setup lang="js">
-import { useUserStore } from '@/store/user'
+import OrderModal from './OrderModal.vue';
+import AddressModal from './AddressModal.vue';
+import CreditModal from './CreditModal.vue';
+import CreditRecordModal from './CreditRecordModal.vue';
+import { onMounted, reactive } from 'vue';
+import { useUserStore } from '@/store/user';
+import { getOrderCount } from '@/api/order';
+
+const userStore = useUserStore();
 
-const userStore = useUserStore()
 defineProps({
   open: {
     type: Boolean,
     default: false
   }
 })
+const emits = defineEmits(['close']);
+
+const mainData = reactive({
+  orderList: [
+    { key: 'NOPAY', name: '待付款', num: 0 },
+    { key: 'DJH', name: '进行中', num: 0 },
+    { key: 'OVER', name: '已完成', num: 0 }
+  ],
+  moreMenusList: [
+    {
+      key: 'openAddress',
+      name: '地址管理'
+    },
+    {
+      key: 'openCredit',
+      name: '我的账单'
+    },
+    {
+      key: 'openRecord',
+      name: '授信记录'
+    }
+  ],
+  orderType: '',
+  openOrder: false,
+  openAddress: false,
+  openCredit: false,
+  openRecord: false
+})
+
+const fetchOrderCount = async () => {
+  const userId = userStore.userInfo?.userId || '';
+  if (!userId) return;
+  const res = await getOrderCount({
+    userId: userId
+  })
+  mainData.orderList[0].num = res.data?.noPay || 0;
+  mainData.orderList[1].num = res.data?.yfh || 0;
+  mainData.orderList[2].num = res.data?.ywc || 0;
+}
 
-const emits = defineEmits(['close'])
+const openOrderModal = type => {
+  mainData.orderType = type;
+  mainData.openOrder = true;
+}
+
+const handleMoreMenus = menu => {
+  mainData[menu.key] = true;
+}
+
+onMounted(() => {
+  fetchOrderCount()
+})
 
 </script>
 

+ 8 - 2
src/pages/home/index.vue

@@ -58,7 +58,7 @@ import Category from './components/Category.vue';
 import LinkList from './components/LinkList.vue';
 import LevelMessage from './components/LevelMessage.vue';
 import { MessageOutlined, ArrowLeftOutlined } from '@ant-design/icons-vue';
-import { onMounted, computed, ref, reactive, nextTick } from 'vue';
+import { onMounted, computed, reactive } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useUserStore } from '@/store/user';
 import { useHomeStore } from '@/store/home';
@@ -143,8 +143,14 @@ const contactUsFn = () => {
 
 onMounted(async () => {
   await storageStore.fetchListData();
+  // 判断本地存储仓库id是否存在
   if (storeList.value && storeList.value.length > 0) {
-    storageStore.updateActivedId(storeList.value[0].storageId);
+    if (storageStore.activedId) {
+      const isExist = storeList.value.find(item => item.storageId == storageStore.activedId);
+      if (!isExist) {
+        storageStore.updateActivedId(storeList.value[0].storageId);
+      }
+    }
     fetchNoticeData();
     fetchgetAmityList();
     isLogin.value && userStore.fetchUserDetail();

+ 0 - 1
src/pages/login/index.vue

@@ -156,7 +156,6 @@ const finish = val => {
 }
 
 const refresh = () => {
-  console.log('refresh')
   fetchVerifiImage()
 }
 

+ 7 - 0
src/router/index.js

@@ -33,6 +33,13 @@ const routes = [
         meta: {
           title: '培训'
         }
+      },
+      {
+        path: '/branch',
+        component: () => import('@/pages/branch/index.vue'),
+        meta: {
+          title: '分支'
+        }
       }
     ]
   },

+ 87 - 0
src/store/location.js

@@ -0,0 +1,87 @@
+import { defineStore } from 'pinia';
+import AMapLoader from '@amap/amap-jsapi-loader';
+
+// 🔑 替换为你的高德 Web Key
+const AMAP_KEY = import.meta.env.VITE_AMAP_KEY;
+const AMAP_VERSION = '2.0';
+console.log(AMAP_KEY);
+
+// 单例:避免重复加载
+let amapInstance = null;
+let loadPromise = null;
+
+async function getAMap() {
+  if (amapInstance) return amapInstance;
+  if (loadPromise) return loadPromise;
+
+  loadPromise = AMapLoader.load({
+    key: AMAP_KEY,        // 申请的高德 Key
+    version: AMAP_VERSION,
+    plugins: ['AMap.Geolocation'], // 预加载 Geolocation 插件
+    Loca: false,          // 不使用 Loca 可视化引擎
+  }).then((AMap) => {
+    amapInstance = AMap;
+    return AMap;
+  });
+
+  return loadPromise;
+}
+
+export const useLocationStore = defineStore('location', {
+  state: () => ({
+    loading: false,
+    error: null,
+    position: null, // { lat: number, lng: number }
+  }),
+
+  actions: {
+    // 获取用户当前位置(经纬度)
+    async getCurrentPosition() {
+      this.loading = true;
+      this.error = null;
+      this.position = null;
+      try {
+        const AMap = await getAMap();
+        const position = await new Promise((resolve, reject) => {
+          // 创建 Geolocation 实例
+          const geolocation = new AMap.Geolocation({
+            enableHighAccuracy: true,
+            timeout: 10000,
+            maximumAge: 0,
+            convert: true,      // 自动纠偏(WGS84 → GCJ-02)
+            showButton: false,
+            panToLocation: false,
+            zoomToAccuracy: false
+          });
+
+          geolocation.getCurrentPosition((status, result) => {
+            console.log('------- 定位结果 ---->', status)
+            console.log('------- 定位结果 ---->', result)
+            if (status === 'complete') {
+              const { lat, lng } = result.position;
+              resolve({ lat, lng });
+            } else {
+              reject(new Error(result.message || '定位失败'));
+            }
+          });
+        });
+
+        this.position = position;
+        console.log('📍 定位成功:', position);
+        return position;
+      } catch (err) {
+        console.error('❌ 定位错误:', err);
+        this.error = err.message || '未知错误';
+        return null;
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    reset() {
+      this.position = null;
+      this.error = null;
+      this.loading = false;
+    }
+  }
+});

+ 5 - 3
src/store/storage.js

@@ -10,10 +10,12 @@ export const useStorageStore = defineStore('storage', {
   actions: {
     async fetchListData() {
       const res = await getStoreList({
-        lat: '23.13',
-        lng: '113.26'
+        // lat: '23.13',
+        // lng: '113.26'
+        lat: '0',
+        lng: '0'
       })
-      this.list = res.data || []
+      this.list = res.data || [];
     },
     updateActivedId(storageId) {
       storeUtil.setId(storageId);

+ 22 - 0
src/utils/index.js

@@ -0,0 +1,22 @@
+export const twoFloatNum = num => {
+  if (isNaN(num) || num === null || num === undefined) {
+    return '';
+  }
+  if (num === '0' || num === 0) {
+    return '0';
+  }
+  const numStr = num.toString();
+  const decimalIndex = numStr.indexOf('.');
+  if (decimalIndex === -1) {
+    return numStr + '.00';
+  }
+  const integerPart = numStr.substring(0, decimalIndex);
+  let decimalPart = numStr.substring(decimalIndex + 1);
+  // 补全或截取小数部分
+  if (decimalPart.length < 2) {
+    decimalPart = decimalPart.padEnd(2, '0');
+  } else {
+    decimalPart = decimalPart.substring(0, 2);
+  }
+  return integerPart + '.' + decimalPart;
+}

+ 1 - 2
vite.config.js

@@ -42,8 +42,7 @@ export default defineConfig(({ mode }) => {
       open: true,
       proxy: {
         // '/api': {
-        //   // target: 'https://jiasm.zfire.top/zfdapi/',
-        //   target: 'https://jiasm.zfire.top',
+        //   target: 'https://jiasm.zfire.top/overseas-miniapp',
         //   ws: true,
         //   changeOrigin: true,
         //   rewrite: (path) => path.replace(/^\/api/, '')