فهرست منبع

零售端修改
1、我的界面改版
2、新增收货地址功能,可设置多个

ouyang 2 هفته پیش
والد
کامیت
98db3955f6

+ 25 - 0
mallApp/src/api/user-address/index.js

@@ -0,0 +1,25 @@
+import https from '@/plugins/luch-request_0.0.7/request'
+
+export const getAddressList = data => {
+	return https.get('/user-address/list', data)
+}
+
+export const getAddressDetail = data => {
+	return https.get('/user-address/detail', data)
+}
+
+export const createAddress = data => {
+	return https.post('/user-address/create', data)
+}
+
+export const updateAddress = data => {
+	return https.post('/user-address/update', data)
+}
+
+export const deleteAddress = data => {
+	return https.post('/user-address/delete', data)
+}
+
+export const setDefaultAddress = data => {
+	return https.post('/user-address/set-default', data)
+}

+ 311 - 0
mallApp/src/components/plugin/region-picker.vue

@@ -0,0 +1,311 @@
+<template>
+	<view class="region-picker" v-if="showPopup" @touchmove.stop.prevent="clear">
+		<!-- 遮罩层 -->
+		<view class="region-picker-mask" @touchmove.stop.prevent="clear" v-if="maskClick" :class="[ani+'-mask', animation ? 'mask-ani' : '']" :style="{
+					'background-color': maskBgColor
+				}"
+		 @tap="hideMask(true)"></view>
+
+		<view class="region-picker-content region-picker--fixed" :class="[type,ani+'-content', animation ? 'content-ani' : '']">
+			<!-- 标题栏 -->
+			<view class="region-picker__header">
+				<view class="region-picker__header-btn-box" @click="pickerCancel">
+					<text class="region-picker__header-text">取消</text>
+				</view>
+				<view class="region-picker__header-title">
+					<text class="region-picker__title-text">选择地区</text>
+				</view>
+				<view class="region-picker__header-btn-box" @click="pickerConfirm">
+					<text class="region-picker__header-text confirm-text">确定</text>
+				</view>
+			</view>
+			
+			<view class="region-picker__box">
+				<picker-view :value="pickerValue" @change="pickerChange" class="region-picker-view">
+					<picker-view-column>
+						<view class="picker-item" v-for="(item,index) in provinceDataList" :key="index">{{item.name}}</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="picker-item" v-for="(item,index) in cityDataList" :key="index">{{item.name}}</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.name}}</view>
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { regionTree } from "@/api/official";
+
+export default {
+	name: 'regionPicker',
+	props: {
+		animation: {
+			type: Boolean,
+			default: true
+		},
+		type: {
+			type: String,
+			default: 'bottom'
+		},
+		maskClick: {
+			type: Boolean,
+			default: true
+		},
+		show: {
+			type: Boolean,
+			default: false
+		},
+		maskBgColor: {
+			type: String,
+			default: 'rgba(0, 0, 0, 0.4)'
+		},
+		pickerValueDefault: {
+			type: Array,
+			default() {
+				return [0, 0, 0]
+			}
+		}
+	},
+	data() {
+		return {
+			ani: '',
+			showPopup: false,
+			pickerValue: [0, 0, 0],
+			provinceDataList: [],
+			cityDataList: [],
+			areaDataList: [],
+			region: []
+		}
+	},
+	watch: {
+		show(newValue) {
+			if (newValue) {
+				this.open()
+			} else {
+				this.close()
+			}
+		},
+		pickerValueDefault() {
+			this.initData()
+		}
+	},
+	created() {
+		this._regionTree()
+	},
+	methods: {
+		_regionTree() {
+			regionTree().then(res => {
+				this.region = res.data.tree || res.data.region || [];
+				this.initData()
+			});
+		},
+		initData() {
+			if (this.region.length === 0) return;
+			
+			this.provinceDataList = this.region;
+			
+			let pIndex = this.pickerValueDefault[0] || 0;
+			let cIndex = this.pickerValueDefault[1] || 0;
+			let aIndex = this.pickerValueDefault[2] || 0;
+			
+			if (pIndex >= this.provinceDataList.length) pIndex = 0;
+			
+			this.cityDataList = this.provinceDataList[pIndex].children || [];
+			if (cIndex >= this.cityDataList.length) cIndex = 0;
+			
+			this.areaDataList = this.cityDataList[cIndex] ? (this.cityDataList[cIndex].children || []) : [];
+			if (aIndex >= this.areaDataList.length) aIndex = 0;
+			
+			this.pickerValue = [pIndex, cIndex, aIndex];
+		},
+		pickerChange(e) {
+			let val = e.detail.value;
+			
+			// 如果省份改变,重置城市和区县
+			if (val[0] !== this.pickerValue[0]) {
+				this.cityDataList = this.provinceDataList[val[0]].children || [];
+				this.areaDataList = this.cityDataList[0] ? (this.cityDataList[0].children || []) : [];
+				val[1] = 0;
+				val[2] = 0;
+			} 
+			// 如果城市改变,重置区县
+			else if (val[1] !== this.pickerValue[1]) {
+				this.areaDataList = this.cityDataList[val[1]] ? (this.cityDataList[val[1]].children || []) : [];
+				val[2] = 0;
+			}
+			
+			this.pickerValue = [...val];
+		},
+		clear() {},
+		hideMask() {
+			this.$emit('onCancel')
+			this.close()
+		},
+		pickerCancel() {
+			this.$emit('onCancel')
+			this.close()
+		},
+		pickerConfirm() {
+			let pIndex = this.pickerValue[0];
+			let cIndex = this.pickerValue[1];
+			let aIndex = this.pickerValue[2];
+			
+			let province = this.provinceDataList[pIndex] || {};
+			let city = this.cityDataList[cIndex] || {};
+			let area = this.areaDataList[aIndex] || {};
+			
+			let pickObj = {
+				value: this.pickerValue,
+				provinceName: province.name || '',
+				cityName: city.name || '',
+				areaName: area.name || '',
+				provinceCode: province.id || '',
+				cityCode: city.id || '',
+				areaCode: area.id || ''
+			};
+			
+			this.$emit('onConfirm', pickObj)
+			this.close()
+		},
+		open() {
+			this.showPopup = true
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.ani = 'simple-' + this.type
+				}, 100)
+			})
+		},
+		close(type) {
+			if (!this.maskClick && type) return
+			this.ani = ''
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.showPopup = false
+					this.$emit('update:show', false)
+				}, 300)
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+	.region-picker {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.region-picker-mask {
+		position: fixed;
+		bottom: 0;
+		top: 0;
+		left: 0;
+		right: 0;
+		transition-property: opacity;
+		transition-duration: 0.3s;
+		opacity: 0;
+		z-index: 996;
+	}
+
+	.mask-ani {
+		transition-property: opacity;
+		transition-duration: 0.2s;
+	}
+
+	.simple-bottom-mask {
+		opacity: 1;
+	}
+
+	.region-picker--fixed {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		transition-property: transform;
+		transition-duration: 0.3s;
+		transform: translateY(1090upx);
+		z-index: 999;
+	}
+
+	.region-picker-content {
+		background-color: #FFFFFF;
+	}
+
+	.content-ani {
+		transition-property: transform, opacity;
+		transition-duration: 0.2s;
+	}
+
+	.simple-bottom-content {
+		transform: translateY(0);
+	}
+
+	.region-picker__header {
+		position: relative;
+		display: flex;
+		flex-direction: row;
+		flex-wrap: nowrap;
+		justify-content: space-between;
+		align-items: center;
+		border-bottom-color: #f2f2f2;
+		border-bottom-style: solid;
+		border-bottom-width: 1upx;
+		padding: 0 20upx;
+	}
+	
+	.region-picker__header-title {
+		flex: 1;
+		text-align: center;
+	}
+	
+	.region-picker__title-text {
+		font-size: 32upx;
+		font-weight: 500;
+		color: #333;
+	}
+
+	.region-picker__header-btn-box {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		height: 90upx;
+	}
+
+	.region-picker__header-text {
+		text-align: center;
+		font-size: 30upx;
+		color: #666;
+		line-height: 90upx;
+		padding-left: 20upx;
+		padding-right: 20upx;
+	}
+
+	.confirm-text {
+		color: #07c160;
+	}
+
+	.region-picker__box {
+		position: relative;
+	}
+
+	.region-picker-view {
+		position: relative;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		height: 500upx;
+		background-color: rgba(255, 255, 255, 1);
+	}
+
+	.picker-item {
+		text-align: center;
+		line-height: 70upx;
+		text-overflow: ellipsis;
+		font-size: 28upx;
+	}
+</style>

+ 6 - 2
mallApp/src/mixins/globalMixins.js

@@ -44,10 +44,14 @@ export default {
 		}
 		if (that.$util.isEmpty(that.loginInfo)) {
 			wxLoginFn().then(wxRes=>{
-				that.init()
+				if (typeof that.init === 'function') {
+					that.init()
+				}
 			})
 		}else{
-			that.init()
+			if (typeof that.init === 'function') {
+				that.init()
+			}
 		}
 	}
 

+ 4 - 2
mallApp/src/pages.json

@@ -14,7 +14,7 @@
         { "path": "pages/home/pic-text-detail", "style": { "navigationBarTitleText": "图文详情" } },
         { "path": "pages/home/mall", "style": { "navigationBarTitleText": "相册" } },
         { "path": "pages/home/shop", "style": { "navigationBarTitleText": "门店" } },
-        { "path": "pages/home/user", "style": { "navigationBarTitleText": "我的" } },
+        { "path": "pages/home/user", "style": { "navigationStyle": "custom", "navigationBarTitleText": "我的" } },
         { "path": "pages/home/order", "style": { "navigationBarTitleText": "订单", "enablePullDownRefresh": true } },
         { "path": "pages/home/course", "style": { "navigationBarTitleText": "课程"}},
         { "path": "pages/item/item", "style": { "navigationBarTitleText": "花材"} },
@@ -24,12 +24,14 @@
         { "path": "pages/class/index", "style": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black" } },
         { "path": "pages/class/paySuccess", "style": { "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black" } }
     ],
-    "subPackages": [{
+    "subPackages": [        {
             "root": "pages/user",
             "pages": [
                 { "path": "register", "style": { "navigationBarTitleText": "注册" } },
                 { "path": "password", "style": { "navigationBarTitleText": "修改密码" } },
                 { "path": "address", "style": { "navigationBarTitleText": "修改地址" } },
+                { "path": "address/list", "style": { "navigationBarTitleText": "地址管理" } },
+                { "path": "address/edit", "style": { "navigationBarTitleText": "添加新地址" } },
                 { "path": "edit", "style": { "navigationBarTitleText": "个人信息" } },
                 { "path": "birthday", "style": { "navigationBarTitleText": "您的生日" } },
                 { "path": "growthList", "style": { "navigationBarTitleText": "成长值记录", "enablePullDownRefresh": true } },

+ 13 - 0
mallApp/src/pages/home/order.vue

@@ -233,6 +233,19 @@ export default {
       if (query.shopNav == 1 || query.shopNav === "1") {
         this.shopNavMode = true;
       }
+      // 「我的」页订单快捷入口通过 switchTabQuery.status 指定 Tab
+      if (query.status !== undefined && query.status !== null && query.status !== "") {
+        this.applyOrderStatus(String(query.status));
+      }
+    },
+
+    /** 按 status key 切换订单 Tab(与 tabs.key 一致) */
+    applyOrderStatus(statusKey) {
+      const tabIdx = this.tabs.findIndex((tab) => tab.key === statusKey);
+      if (tabIdx >= 0) {
+        this.tabIndex = tabIdx;
+        this.status = statusKey;
+      }
     },
 
     /** 店铺模式隐藏原生 tabBar,普通 tab 入口则显示原生 tabBar */

+ 509 - 161
mallApp/src/pages/home/user.vue

@@ -1,201 +1,549 @@
+<!--
+  商城「我的」Tab 页
+  展示用户信息、订单快捷入口与常用服务菜单,配合原生 tabBar 使用。
+-->
 <template>
-  <view class="app-main app-content">
-    <view class="user-wrap">
-      <template>
-        <view class="is-login-attr">
-          <view class="user-wrap-top">
-            <view class="user-blo-left">
-              <view class="user-name-warp" v-if="!$util.isEmpty(loginInfo)"> <view class="user-name">{{loginInfo.name||'游客'}}</view> </view>
-              <view class="user-name-warp" @click="pageTo({url:'/pages/login/index'})" v-else> <view class="user-name">注册/登录</view> </view>
-              <view class="user-level" v-if="!$util.isEmpty(loginInfo)"><view>ID {{loginInfo.id||'0000'}}</view></view>
-            </view>
-            <view class="user-blo-right login-attr-wrap">
-              <image :src="`${constant.imgUrl}/default-img.png`" mode="widthFix"></image>
-            </view>
+  <view class="user-page">
+    <!-- 自定义顶栏:绿色标题 + 消息/设置 -->
+    <view class="user-nav">
+      <view class="user-nav__status" :style="{ height: navMetrics.statusBarHeight + 'px' }"></view>
+      <view class="user-nav__bar" :style="{ height: navMetrics.navBarHeight + 'px' }">
+        <view class="user-nav__title-wrap" :style="titleWrapStyle">
+          <text class="user-nav__title">我的</text>
+        </view>
+        <view class="user-nav__actions">
+          <view class="user-nav__action" @click="goChat">
+            <sprite-icon name="xiaoxi" :size="40" custom-class="nav-icon" />
+          </view>
+          <view class="user-nav__action user-nav__action--settings" @click="goSettings">
+            <sprite-icon name="shezhi" :size="36" custom-class="nav-icon" />
           </view>
         </view>
-      </template>
+      </view>
     </view>
-    <template>
-      <view class="surplus-wrap">
-        <view class="surplus-left">
-          <view>余额</view>
-          <view class="my-surplus">0</view>
+
+    <view class="user-body" :style="bodyStyle">
+      <!-- 用户信息卡片 -->
+      <view class="profile-card" @click="onProfileClick">
+        <view class="profile-card__bg"></view>
+        <view class="profile-card__content">
+          <view class="profile-avatar">
+            <image
+              class="profile-avatar__img"
+              :src="avatarUrl"
+              mode="aspectFill"
+            />
+          </view>
+          <view class="profile-info">
+            <text class="profile-name">{{ displayName }}</text>
+            <text v-if="isLoggedIn" class="profile-id">会员ID: {{ memberId }}</text>
+            <text v-else class="profile-id profile-id--hint">登录后查看会员信息</text>
+          </view>
         </view>
       </view>
-    </template>
-    <view class="list-content" v-if="!$util.isEmpty(loginInfo)">
-      <tui-list-view class="tui-list-view">
-        <tui-list-cell class="tui-list" :arrow="true" @click="pageToFn({ url: '/pages/home/order', type: 4 })" >
-          <view class="list-wrap">
-            <view class="list-label">我的订单</view>
+
+      <!-- 我的订单 -->
+      <view class="section-card">
+        <view class="section-header">
+          <text class="section-title">我的订单</text>
+          <view class="section-link" @click="goAllOrders">
+            <text class="section-link__text">查看全部</text>
+            <text class="section-link__arrow">›</text>
           </view>
-        </tui-list-cell>
-        <tui-list-cell class="tui-list" :arrow="true" @click="pageToFn('/pages/coupon/list')" >
-          <view class="list-wrap">
-            <view class="list-label">我的优惠券</view>
+        </view>
+        <view class="order-grid">
+          <view
+            v-for="(item, index) in orderShortcuts"
+            :key="item.key"
+            class="order-item"
+            :class="{ 'order-item--first': index === 0 }"
+            @click="goOrderStatus(item)"
+          >
+            <view class="order-icon-wrap" :class="'order-icon-wrap--' + item.theme">
+              <sprite-icon :name="item.icon" :size="item.iconSize" custom-class="order-icon" />
+            </view>
+            <text class="order-item__label">{{ item.name }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 我的服务 -->
+      <view class="section-card">
+        <view class="section-header section-header--solo">
+          <text class="section-title">我的服务</text>
+        </view>
+        <view class="service-list">
+          <view
+            v-for="(item, index) in serviceItems"
+            :key="item.key"
+            class="service-item"
+            :class="{ 'service-item--first': index === 0 }"
+            @click="onServiceClick(item)"
+          >
+            <view class="service-item__left">
+              <sprite-icon
+                v-if="item.icon"
+                :name="item.icon"
+                :size="item.iconSize || 48"
+                custom-class="service-icon"
+              />
+              <text v-else class="iconfont service-iconfont" :class="item.iconfont"></text>
+              <text class="service-item__label">{{ item.name }}</text>
+            </view>
+            <text class="service-item__arrow">›</text>
           </view>
-        </tui-list-cell>
-      </tui-list-view>
+        </view>
+      </view>
     </view>
   </view>
 </template>
+
 <script>
+/**
+ * 商城「我的」页
+ * 原生 tabBar 入口,展示会员信息、订单状态快捷入口与服务菜单
+ */
 import { mapGetters } from "vuex";
-import TuiListView from "@/components/plugin/list-view";
-import TuiListCell from "@/components/plugin/list-cell";
-import LoginPopup from "@/pages/home/components/login-popup";
-import AppTrademark from "@/components/module/app-trademark";
-import GoLogin from "@/pages/home/components/go-login.vue";
-import AppVipModule from "@/components/module/app-vip";
+import SpriteIcon from "@/components/sprite-icon/index.vue";
+import { getNavBarMetrics } from "@/utils/navBar";
 import { getShopUser } from "@/utils/auth";
+import { clearLogin } from "@/api/user";
+
 export default {
   name: "user",
   components: {
-    TuiListView,
-    TuiListCell,
-    LoginPopup,
-    AppTrademark,
-    GoLogin,
-    AppVipModule,
+    SpriteIcon
   },
   data() {
     return {
       constant: this.$constant,
-      popupShow: false,
-      isPublic: false,
-      stepIndex: 0,
-      wxInfo: {},
-      loginShow: false,
-      merchantInfo: {},
-      mobile: "",
-      subscribe: 0,
-      currentTabIndex: 4
+      navMetrics: getNavBarMetrics(),
+      /** 订单快捷入口,status 对应 order.vue tabs.key */
+      orderShortcuts: [
+        { key: "pay", name: "待付款", icon: "qianbao", iconSize: 48, status: "1", theme: "pink" },
+        { key: "delivery", name: "待配送", icon: "huoche-da", iconSize: 48, status: "2", theme: "green" },
+        { key: "receive", name: "待收货", icon: "baoguo", iconSize: 48, status: "3", theme: "green" },
+        { key: "refund", name: "退款/售后", icon: "fanghu-yuan", iconSize: 48, status: "0", theme: "pink" }
+      ],
+      /** 服务菜单 */
+      serviceItems: [
+        { key: "coupon", name: "优惠券", icon: "youhuiquan-yellow", iconSize: 40, url: "/pages/hb/list" },
+        { key: "addressNew", name: "收货地址", iconfont: "iconditu", url: "/pages/user/address/list" },
+        { key: "hasDel", name: "已删花店", icon: "dianpu-fill", iconSize: 40, url: "/pages/hd/hasDel" },
+        { key: "logout", name: "退出登录", icon: "tuichu", iconSize: 40, action: "logout" }
+      ]
     };
   },
-	computed: {
-		...mapGetters({loginInfo:"getLoginInfo"})
-	},
-  onLoad(option) {
-  },
-  methods: {
-    init() {
-      getShopUser(true).then(res => {})
+  computed: {
+    ...mapGetters({ loginInfo: "getLoginInfo" }),
+    isLoggedIn() {
+      return !this.$util.isEmpty(this.loginInfo);
+    },
+    displayName() {
+      if (!this.isLoggedIn) {
+        return "注册/登录";
+      }
+      return this.loginInfo.name || "游客";
+    },
+    memberId() {
+      return this.loginInfo.id || "--";
     },
-    pageToFn(item) {
-        this.$util.pageTo(item)
+    avatarUrl() {
+      if (this.loginInfo && this.loginInfo.avatar) {
+        return this.loginInfo.avatar;
+      }
+      return `${this.constant.imgUrl}/retail/default-img.png`;
+    },
+    titleWrapStyle() {
+      const inset = this.navMetrics.titleSideInset + "px";
+      return {
+        paddingLeft: inset,
+        paddingRight: inset
+      };
+    },
+    bodyStyle() {
+      const paddingTop = this.navMetrics.navTotalHeight + uni.upx2px(24);
+      return `padding-top: ${paddingTop}px;`;
     }
   },
-};
-</script>
-<style lang="scss" scoped>
-.user-wrap {
-  height: 220upx;
-  background-image: url("../../static/images/user/bg.png");
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-  padding-top: 42upx;
-  color: #f5e5e5;
-  .login-attr-wrap {
-    width: 90upx;
-    height: 90upx;
-    border-radius: 50%;
-    image {
-      border-radius: 50%;
+  onShow() {
+    if (this.isLoggedIn) {
+      getShopUser(true).then(() => {});
     }
-  }
-  .no-login-attr {
-    margin: 0 auto;
-    margin-bottom: 12upx;
-  }
-  .is-login-attr {
-    padding: 0 20upx 0 48upx;
-    .user-wrap-top {
-      @include disFlex(center, space-between);
-      .user-name-warp {
-        @include disFlex(center, flex-start);
-        margin-bottom: 16upx;
-        .user-name {
-          font-size: 36upx;
-          font-weight: 600;
-          color: #fff;
-          margin-right: 20upx;
-        }
-        .level-img {
-          ::v-deep.vip-text {
-            top: 4upx;
-            left: 42upx;
-            transform: scale(0.8);
-          }
-        }
-        .nomal-text {
-          font-size: 24upx;
-          padding: 2upx 16upx;
-          color: #f7f7f7;
-          border: 1upx solid #f7f7f7;
-          display: inline-block;
-          border-radius: 50upx;
-        }
+  },
+  methods: {
+    /** 未登录时引导登录 */
+    requireLogin() {
+      if (this.isLoggedIn) {
+        return true;
       }
-      .user-level {
-        @include disFlex(center, flex-start);
+      this.pageTo({ url: "/pages/login/index?needBack=1" });
+      return false;
+    },
+    onProfileClick() {
+      if (!this.isLoggedIn) {
+        this.pageTo({ url: "/pages/login/index?needBack=1" });
+        return;
       }
-      .user-blo-right {
-        margin-right: 30upx;
+      this.pageTo({ url: "/pages/user/edit" });
+    },
+    goChat() {
+      if (!this.requireLogin()) {
+        return;
       }
-    }
-    .user-wrap-bottom {
-      .level-lint {
-        border-bottom: 4upx solid $mainColor;
-        border-radius: 4upx;
-        margin-top: 20upx;
-        margin-bottom: 10upx;
-        position: relative;
+      this.pageTo({ url: "/pages/chat/list" });
+    },
+    goSettings() {
+      if (!this.requireLogin()) {
+        return;
+      }
+      this.pageTo({ url: "/pages/user/edit" });
+    },
+    /** 跳转订单 Tab,全部订单 */
+    goAllOrders() {
+      if (!this.requireLogin()) {
+        return;
+      }
+      this.pageTo({
+        url: "/pages/home/order",
+        type: 4,
+        query: { status: "0" }
+      });
+    },
+    /** 按订单状态跳转订单 Tab */
+    goOrderStatus(item) {
+      if (!this.requireLogin()) {
+        return;
+      }
+      this.pageTo({
+        url: "/pages/home/order",
+        type: 4,
+        query: { status: item.status || "0" }
+      });
+    },
+    onServiceClick(item) {
+      if (item.action === "logout") {
+        this.handleLogout();
+        return;
+      }
+      if (!this.requireLogin()) {
+        return;
+      }
+      if (item.url) {
+        this.pageTo({ url: item.url });
+      }
+    },
+    /** 退出登录,逻辑与个人信息页保持一致 */
+    handleLogout() {
+      if (!this.isLoggedIn) {
+        this.$msg("当前未登录");
+        return;
       }
+      this.$util.confirmModal({ content: "确认退出?" }, () => {
+        // #ifdef APP-PLUS
+        uni.clearStorage();
+        this.$store.commit("setLoginInfo", {});
+        plus.runtime.restart();
+        // #endif
+        // #ifdef MP-WEIXIN
+        clearLogin().then((res) => {
+          if (res.code == 1) {
+            uni.clearStorage();
+            this.$store.commit("setLoginInfo", {});
+            uni.reLaunch({ url: "/pages/home/recent" });
+          }
+        });
+        // #endif
+        // #ifdef H5
+        uni.clearStorage();
+        this.$store.commit("setLoginInfo", {});
+        uni.reLaunch({ url: "/pages/home/recent" });
+        // #endif
+      });
     }
   }
-  .button-wrap {
-    @include disFlex(center, center);
+};
+</script>
 
-    .mini-btn {
-      border: none !important;
-    }
-  }
+<style lang="scss" scoped>
+.user-page {
+  min-height: 100vh;
+  background: #f3f4f6;
+  padding-bottom: calc(20upx + env(safe-area-inset-bottom));
 }
-.surplus-wrap {
-  @include disFlex(flex-end, space-between);
-  padding: 10upx 40upx 34upx;
-  background-color: #fff;
-  .surplus-left {
-    color: $fontColor3;
-    .my-surplus {
-      font-size: 46upx;
-      margin-top: 14upx;
-      color: #333;
-      font-weight: bold;
-    }
-  }
-  .surplus-right {
-    margin-bottom: 10upx;
-    .button-com {
-      margin-left: 20upx;
-    }
+
+.user-nav {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 100;
+  background-color: #ffffff;
+}
+
+.user-nav__bar {
+  position: relative;
+  display: flex;
+  align-items: center;
+  box-sizing: border-box;
+}
+
+.user-nav__title-wrap {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-sizing: border-box;
+  pointer-events: none;
+}
+
+.user-nav__title {
+  font-size: 34upx;
+  font-weight: 700;
+  color: #77c34f;
+}
+
+.user-nav__actions {
+  position: relative;
+  z-index: 2;
+  margin-left: auto;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding-right: 24upx;
+}
+
+.user-nav__action {
+  width: 72upx;
+  height: 72upx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: 8upx;
+
+  &:first-child {
+    margin-left: 0;
   }
 }
-.list-content {
-  margin-bottom: 20upx;
-  background-color: #fff;
-  .tui-list-cell {
-    padding-left: 50upx;
-    padding-right: 50upx;
+
+.user-nav__action--settings {
+  margin-left: 4upx;
+}
+
+.user-body {
+  padding-left: 24upx;
+  padding-right: 24upx;
+  box-sizing: border-box;
+}
+
+.profile-card {
+  position: relative;
+  border-radius: 24upx;
+  overflow: hidden;
+  margin-bottom: 24upx;
+  box-shadow: 0 8upx 24upx rgba(15, 23, 42, 0.06);
+}
+
+.profile-card__bg {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background: linear-gradient(135deg, #fce7f3 0%, #fdf2f8 45%, #ffffff 100%);
+}
+
+.profile-card__content {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 36upx 32upx;
+}
+
+.profile-avatar {
+  flex-shrink: 0;
+  width: 120upx;
+  height: 120upx;
+  border-radius: 50%;
+  overflow: hidden;
+  border: 4upx solid rgba(255, 255, 255, 0.9);
+  background: #fff;
+}
+
+.profile-avatar__img {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.profile-info {
+  flex: 1;
+  min-width: 0;
+  margin-left: 28upx;
+  display: flex;
+  flex-direction: column;
+}
+
+.profile-name {
+  font-size: 36upx;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.3;
+  margin-bottom: 12upx;
+}
+
+.profile-id {
+  font-size: 24upx;
+  color: #9ca3af;
+  line-height: 1.4;
+}
+
+.profile-id--hint {
+  color: #77c34f;
+}
+
+.section-card {
+  background: #ffffff;
+  border-radius: 24upx;
+  padding: 28upx 28upx 24upx;
+  margin-bottom: 24upx;
+  box-shadow: 0 8upx 24upx rgba(15, 23, 42, 0.04);
+}
+
+.section-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 28upx;
+}
+
+.section-header--solo {
+  margin-bottom: 8upx;
+}
+
+.section-title {
+  font-size: 32upx;
+  font-weight: 700;
+  color: #1f2937;
+}
+
+.section-link {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.section-link__text {
+  font-size: 24upx;
+  color: #9ca3af;
+}
+
+.section-link__arrow {
+  font-size: 28upx;
+  color: #9ca3af;
+  margin-left: 4upx;
+  line-height: 1;
+}
+
+.order-grid {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+}
+
+.order-item {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-left: 12upx;
+
+  &--first {
+    margin-left: 0;
   }
-  .list-wrap {
-    @include disFlex(center, space-between);
-    width: calc(100% - 30upx);
-    padding-right: 30upx;
-    .list-text {
-      color: $fontColor3;
-    }
+}
+
+.order-icon-wrap {
+  width: 96upx;
+  height: 96upx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 12upx;
+}
+
+.order-icon-wrap--pink {
+  background: #fce7f3;
+}
+
+.order-icon-wrap--green {
+  background: #ecfdf3;
+}
+
+.order-item__label {
+  font-size: 24upx;
+  color: #4b5563;
+  text-align: center;
+  line-height: 1.3;
+}
+
+.service-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.service-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 28upx 0;
+  border-top: 1upx solid #f3f4f6;
+  margin-top: 20upx;
+
+  &--first {
+    margin-top: 0;
+    border-top: none;
   }
 }
-</style>
+
+.service-item__left {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex: 1;
+  min-width: 0;
+}
+
+.service-item__left ::v-deep .service-icon {
+  margin-right: 20upx;
+}
+
+.service-iconfont {
+  font-size: 36upx;
+  color: #3b82f6;
+  margin-right: 20upx;
+  flex-shrink: 0;
+}
+
+.service-item__label {
+  font-size: 30upx;
+  color: #374151;
+}
+
+.service-item__arrow {
+  font-size: 32upx;
+  color: #d1d5db;
+  flex-shrink: 0;
+  margin-left: 16upx;
+}
+</style>

+ 504 - 0
mallApp/src/pages/user/address/edit.vue

@@ -0,0 +1,504 @@
+<template>
+	<view class="address-edit-page">
+		<view class="safe-banner">
+			<view class="safe-icon">
+				<text class="iconfont iconchenggong" style="color: #07c160; font-size: 48upx;"></text>
+			</view>
+			<view class="safe-text">
+				<view class="title">保障您的收货信息安全</view>
+				<view class="desc">加密存储,严格保护您的隐私</view>
+			</view>
+			<text class="iconfont iconxiangyou" style="color: #999; font-size: 32upx;"></text>
+		</view>
+
+		<view class="form-wrap">
+			<view class="form-item">
+				<view class="label">收货人<text class="required">*</text></view>
+				<input class="input" type="text" v-model="form.name" placeholder="请填写收货人姓名" placeholder-class="placeholder" />
+				<text class="iconfont iconshanchu" v-if="form.name" @click="form.name = ''" style="color: #ccc; font-size: 32upx; padding: 10upx;"></text>
+			</view>
+			
+			<view class="form-item">
+				<view class="label">手机号<text class="required">*</text></view>
+				<input class="input" type="number" maxlength="11" v-model="form.phone" placeholder="请填写收货人手机号" placeholder-class="placeholder" />
+				<text class="iconfont iconshanchu" v-if="form.phone" @click="form.phone = ''" style="color: #ccc; font-size: 32upx; padding: 10upx;"></text>
+			</view>
+			
+			<view class="form-item" @click="openAddressPicker">
+				<view class="label">所在地区<text class="required">*</text></view>
+				<view class="input-wrap">
+					<view class="value" v-if="form.province">{{ form.province }} {{ form.city }} {{ form.dist }}</view>
+					<view class="placeholder" v-else>请选择省 / 市 / 区</view>
+				</view>
+				<text class="iconfont iconxiangyou" style="color: #999; font-size: 32upx;"></text>
+			</view>
+			
+			<view class="form-item align-top" @click="selectRegion">
+				<view class="label">详细地址<text class="required">*</text></view>
+				<view class="input-wrap">
+					<view class="value" v-if="form.address">{{ form.address }}</view>
+					<view class="placeholder" v-else>请选择详细地址</view>
+				</view>
+				<text class="iconfont iconxiangyou" style="color: #999; font-size: 32upx;"></text>
+			</view>
+			
+			<view class="form-item">
+				<view class="label">门牌号</view>
+				<view class="input-wrap">
+					<input class="input" type="text" v-model="form.floor" placeholder="如 1 号楼 2单元 101室 (选填)" placeholder-class="placeholder" />
+				</view>
+				<text class="iconfont iconshanchu" v-if="form.floor" @click="form.floor = ''" style="color: #ccc; font-size: 32upx; padding: 10upx;"></text>
+			</view>
+			
+			<view class="form-item tag-item">
+				<view class="label">
+					<view>地址标签</view>
+					<view class="sub-label">(选填)</view>
+				</view>
+				<view class="tags">
+					<view 
+						class="tag" 
+						:class="{ active: form.tag === tag }" 
+						v-for="(tag, index) in tags" 
+						:key="index"
+						@click="selectTag(tag)"
+					>
+						{{ tag }}
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<view class="location-wrap" @click="chooseLocation">
+			<view class="icon-wrap">
+				<text class="iconfont iconditu" style="color: #07c160; font-size: 32upx;"></text>
+			</view>
+			<view class="text-wrap">
+				<view class="title">定位当前地址</view>
+				<view class="desc">使用当前位置自动填写地址信息</view>
+			</view>
+			<text class="iconfont iconxiangyou" style="color: #999; font-size: 32upx;"></text>
+		</view>
+
+		<view class="default-wrap">
+			<view class="text-wrap">
+				<view class="title">设为默认收货地址</view>
+				<view class="desc">开启后,该地址将作为下单时的默认收货地址</view>
+			</view>
+			<switch :checked="form.default === 1" color="#07c160" @change="defaultChange" style="transform:scale(0.8)" />
+		</view>
+
+		<view class="bottom-btn-wrap">
+			<button class="save-btn" @click="saveAddress">保存并使用</button>
+		</view>
+
+		<!-- 省市区选择器 -->
+		<region-picker ref="regionPicker" :pickerValueDefault="cityPickerValueDefault" @onConfirm="onCityConfirm"></region-picker>
+		
+		<!-- 选择地区 -->
+		<app-area-sel :show.sync="showRegion" :city="form.city" @change="changeAreaFn" :isFocus="false" />
+	</view>
+</template>
+
+<script>
+/**
+ * 用户收货地址编辑/新增
+ * 解决用户添加和修改收货地址的问题
+ */
+import RegionPicker from '@/components/plugin/region-picker'
+import AppAreaSel from '@/components/app-area-sel'
+import { getAddressDetail, createAddress, updateAddress } from '@/api/user-address'
+
+export default {
+	components: {
+		RegionPicker,
+		AppAreaSel
+	},
+	data() {
+		return {
+			id: '',
+			form: {
+				name: '',
+				phone: '',
+				province: '',
+				city: '',
+				dist: '',
+				address: '',
+				floor: '',
+				tag: '',
+				default: 0,
+				lat: '',
+				long: ''
+			},
+			tags: ['家', '公司', '学校', '父母家', '朋友家'],
+			cityPickerValueDefault: [0, 0, 0],
+			showRegion: false
+		}
+	},
+	onLoad(options) {
+		if (options.id) {
+			this.id = options.id
+			uni.setNavigationBarTitle({ title: '编辑地址' })
+			this.getDetail()
+		} else {
+			uni.setNavigationBarTitle({ title: '添加新地址' })
+		}
+	},
+	// 导航栏右侧按钮点击事件
+	onNavigationBarButtonTap(e) {
+		if (e.index === 0) {
+			this.saveAddress()
+		}
+	},
+	methods: {
+		// 获取详情
+		getDetail() {
+			uni.showLoading({ title: '加载中' })
+			getAddressDetail({ id: this.id }).then(res => {
+				uni.hideLoading()
+				if (res.code === 1) {
+					// 假设后端返回的数据在 res.data.info
+					let data = res.data.info || res.data || {}
+					Object.keys(this.form).forEach(key => {
+						if (data[key] !== undefined) {
+							this.form[key] = data[key]
+						}
+					})
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		},
+		// 打开地址选择器
+		openAddressPicker() {
+			this.$refs.regionPicker.open()
+		},
+		// 地址选择确认
+		onCityConfirm(e) {
+			this.form.province = e.provinceName
+			this.form.city = e.cityName
+			this.form.dist = e.areaName
+			this.form.address = '' // 切换城市后清空详细地址
+			this.form.lat = ''
+			this.form.long = ''
+		},
+		// 打开详细地址选择
+		selectRegion() {
+			if (!this.form.city) {
+				uni.showToast({ title: '请先选择所在地区', icon: 'none' })
+				return false
+			}
+			this.showRegion = true
+		},
+		// 详细地址选择确认
+		changeAreaFn(e) {
+			if (e.location) {
+				let locationStr = e.location
+				let fruits = locationStr.split(",").map(fruit => fruit.trim())
+				this.form.lat = fruits[1]
+				this.form.long = fruits[0]
+			}
+			this.form.address = e.name
+			// 处理 e.address 可能是空数组的情况
+			let showAddr = '';
+			if (typeof e.address === 'string') {
+				showAddr = e.address;
+			} else if (Array.isArray(e.address)) {
+				showAddr = e.address.join('');
+			}
+			this.form.showAddress = showAddr;
+		},
+		// 选择标签
+		selectTag(tag) {
+			if (this.form.tag === tag) {
+				this.form.tag = '' // 取消选择
+			} else {
+				this.form.tag = tag
+			}
+		},
+		// 选择位置
+		chooseLocation() {
+			uni.chooseLocation({
+				success: (res) => {
+					this.form.address = res.name
+					this.form.lat = res.latitude
+					this.form.long = res.longitude
+					// 尝试解析省市区 (实际项目中可能需要调用地图API进行逆地址解析)
+					// 这里只是简单示例,实际应用中建议接入腾讯地图等API
+				}
+			})
+		},
+		// 默认地址切换
+		defaultChange(e) {
+			this.form.default = e.detail.value ? 1 : 0
+		},
+		// 验证表单
+		validate() {
+			if (!this.form.name) {
+				uni.showToast({ title: '请填写收货人姓名', icon: 'none' })
+				return false
+			}
+			if (!this.form.phone) {
+				uni.showToast({ title: '请填写手机号', icon: 'none' })
+				return false
+			}
+			if (!/^1\d{10}$/.test(this.form.phone)) {
+				uni.showToast({ title: '手机号格式不正确', icon: 'none' })
+				return false
+			}
+			if (!this.form.province || !this.form.city || !this.form.dist) {
+				uni.showToast({ title: '请选择所在地区', icon: 'none' })
+				return false
+			}
+			if (!this.form.address) {
+				uni.showToast({ title: '请选择详细地址', icon: 'none' })
+				return false
+			}
+			return true
+		},
+		// 保存地址
+		saveAddress() {
+			if (!this.validate()) return
+			
+			// 组合完整地址
+			this.form.fullAddress = `${this.form.province}${this.form.city}${this.form.dist}${this.form.address}${this.form.floor}`
+			
+			uni.showLoading({ title: '保存中' })
+			const api = this.id ? updateAddress : createAddress
+			const data = this.id ? { ...this.form, id: this.id } : this.form
+			api(data).then(res => {
+				uni.hideLoading()
+				if (res.code === 1) {
+					uni.showToast({ title: '保存成功', icon: 'success' })
+					setTimeout(() => {
+						uni.navigateBack()
+					}, 1500)
+				} else {
+					uni.showToast({ title: res.msg || '保存失败', icon: 'none' })
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.address-edit-page {
+	min-height: 100vh;
+	background-color: #f5f5f5;
+	padding: 20upx;
+	padding-bottom: 140upx;
+}
+
+.safe-banner {
+	display: flex;
+	align-items: center;
+	background-color: #f0f9f0;
+	border-radius: 16upx;
+	padding: 20upx 30upx;
+	margin-bottom: 20upx;
+	
+	.safe-icon {
+		margin-right: 20upx;
+	}
+	
+	.safe-text {
+		flex: 1;
+		.title {
+			font-size: 28upx;
+			color: #333;
+			font-weight: bold;
+			margin-bottom: 4upx;
+		}
+		.desc {
+			font-size: 24upx;
+			color: #666;
+		}
+	}
+}
+
+.form-wrap {
+	background-color: #fff;
+	border-radius: 16upx;
+	padding: 0 30upx;
+	margin-bottom: 20upx;
+	
+	.form-item {
+		display: flex;
+		align-items: center;
+		padding: 30upx 0;
+		border-bottom: 1upx solid #eee;
+		
+		&:last-child {
+			border-bottom: none;
+		}
+		
+		&.align-top {
+			align-items: flex-start;
+		}
+		
+		.label {
+			width: 160upx;
+			font-size: 30upx;
+			color: #333;
+			
+			.required {
+				color: #ff4d4f;
+				margin-left: 4upx;
+			}
+			
+			.sub-label {
+				font-size: 24upx;
+				color: #999;
+				margin-top: 4upx;
+			}
+		}
+		
+		.input-wrap {
+			flex: 1;
+			
+			.value {
+				font-size: 30upx;
+				color: #333;
+			}
+			
+			.placeholder {
+				font-size: 30upx;
+				color: #ccc;
+			}
+		}
+		
+		.input {
+			flex: 1;
+			font-size: 30upx;
+			color: #333;
+		}
+		
+		.textarea {
+			width: 100%;
+			font-size: 30upx;
+			color: #333;
+			min-height: 80upx;
+			line-height: 1.5;
+		}
+		
+		.placeholder {
+			color: #ccc;
+		}
+	}
+	
+	.tag-item {
+		align-items: flex-start;
+		
+		.tags {
+			flex: 1;
+			display: flex;
+			flex-wrap: wrap;
+			
+			.tag {
+				padding: 8upx 24upx;
+				background-color: #f5f5f5;
+				color: #666;
+				font-size: 26upx;
+				border-radius: 30upx;
+				border: 2upx solid transparent;
+				margin-right: 20upx;
+				margin-bottom: 20upx;
+				
+				&.active {
+					background-color: rgba(7, 193, 96, 0.1);
+					color: #07c160;
+					border-color: #07c160;
+				}
+			}
+		}
+	}
+}
+
+.location-wrap {
+	display: flex;
+	align-items: center;
+	background-color: #fff;
+	border-radius: 16upx;
+	padding: 30upx;
+	margin-bottom: 20upx;
+	
+	.icon-wrap {
+		width: 60upx;
+		height: 60upx;
+		background-color: rgba(7, 193, 96, 0.1);
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 20upx;
+	}
+	
+	.text-wrap {
+		flex: 1;
+		
+		.title {
+			font-size: 30upx;
+			color: #333;
+			margin-bottom: 4upx;
+		}
+		
+		.desc {
+			font-size: 24upx;
+			color: #999;
+		}
+	}
+}
+
+.default-wrap {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	background-color: #fff;
+	border-radius: 16upx;
+	padding: 30upx;
+	margin-bottom: 40upx;
+	
+	.text-wrap {
+		.title {
+			font-size: 30upx;
+			color: #333;
+			margin-bottom: 4upx;
+		}
+		
+		.desc {
+			font-size: 24upx;
+			color: #999;
+		}
+	}
+}
+
+.bottom-btn-wrap {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	padding: 20upx 30upx;
+	background-color: #fff;
+	box-shadow: 0 -2upx 10upx rgba(0,0,0,0.05);
+	box-sizing: border-box;
+	z-index: 99;
+	
+	.save-btn {
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 40upx;
+		font-size: 32upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 80upx;
+		
+		&::after {
+			border: none;
+		}
+	}
+}
+</style>

+ 356 - 0
mallApp/src/pages/user/address/list.vue

@@ -0,0 +1,356 @@
+<template>
+	<view class="address-list-page">
+		<view class="safe-banner">
+			<view class="safe-icon">
+				<text class="iconfont iconchenggong" style="color: #07c160; font-size: 48upx;"></text>
+			</view>
+			<view class="safe-text">
+				<view class="title">保障您的收货信息安全</view>
+				<view class="desc">加密存储,严格保护您的隐私</view>
+			</view>
+			<text class="iconfont iconxiangyou" style="color: #999; font-size: 32upx;"></text>
+		</view>
+
+		<view class="address-list">
+			<view 
+				class="address-item" 
+				:class="{ 'is-default': item.default == 1 }"
+				v-for="(item, index) in list" 
+				:key="item.id"
+			>
+				<view class="default-badge" v-if="item.default == 1">默认地址</view>
+				
+				<view class="info-top">
+					<text class="name">{{ item.name || '未填写' }}</text>
+					<text class="phone">{{ item.phone || '未填写' }}</text>
+					<text class="tag default-tag" v-if="item.default == 1">默认</text>
+					<text class="tag custom-tag" v-if="item.tag">{{ item.tag }}</text>
+				</view>
+				
+				<view class="address-detail">
+					<text>{{ item.province }} {{ item.city }} {{ item.dist }}</text>
+					<text>{{ item.address }}</text>
+					<text>{{ item.floor }}</text>
+				</view>
+				
+				<view class="distance" v-if="item.distance">
+					<text class="iconfont iconditu" style="color: #07c160; font-size: 28upx;"></text>
+					<text>距离您 {{ item.distance }}</text>
+				</view>
+
+				<view class="action-bar">
+					<view class="left-action">
+						<view class="set-default" v-if="item.default != 1" @click="setDefault(item)">
+							<text class="iconfont iconweixuanzhong" style="color: #ccc; font-size: 36upx; margin-right: 10upx;"></text>
+							<text>设为默认</text>
+						</view>
+						<view class="set-default" v-else>
+							<text class="iconfont iconxuanzhong" style="color: #07c160; font-size: 36upx; margin-right: 10upx;"></text>
+							<text style="color: #07c160;">设为默认</text>
+						</view>
+					</view>
+					<view class="right-action">
+						<view class="action-btn" @click="editAddress(item)">
+							<text class="iconfont iconbianji" style="color: #666; font-size: 32upx;"></text>
+							<text>编辑</text>
+						</view>
+						<view class="action-btn" @click="deleteItem(item)">
+							<text class="iconfont iconshanchu1" style="color: #666; font-size: 32upx;"></text>
+							<text>删除</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<view class="bottom-btn-wrap">
+			<button class="add-btn" @click="addAddress">
+				<text class="plus">+</text> 添加新地址
+			</button>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * 用户收货地址列表
+ * 解决用户管理收货地址的问题
+ */
+import { getAddressList, deleteAddress, setDefaultAddress } from '@/api/user-address'
+
+export default {
+	data() {
+		return {
+			list: []
+		}
+	},
+	onLoad() {
+	},
+	onShow() {
+		this.getList()
+	},
+	methods: {
+		// 获取地址列表
+		getList() {
+			uni.showLoading({ title: '加载中' })
+			getAddressList().then(res => {
+				uni.hideLoading()
+				if (res.code === 1) {
+					this.list = res.data.list || res.data || []
+				} else {
+					uni.showToast({ title: res.msg || '获取列表失败', icon: 'none' })
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		},
+		// 添加地址
+		addAddress() {
+			uni.navigateTo({
+				url: '/pages/user/address/edit'
+			})
+		},
+		// 编辑地址
+		editAddress(item) {
+			uni.navigateTo({
+				url: `/pages/user/address/edit?id=${item.id}`
+			})
+		},
+		// 删除地址
+		deleteItem(item) {
+			uni.showModal({
+				title: '提示',
+				content: '确定要删除该地址吗?',
+				success: (res) => {
+					if (res.confirm) {
+						uni.showLoading({ title: '删除中' })
+						deleteAddress({ id: item.id }).then(res => {
+							uni.hideLoading()
+							if (res.code === 1) {
+								uni.showToast({ title: '删除成功', icon: 'success' })
+								this.getList()
+							} else {
+								uni.showToast({ title: res.msg || '删除失败', icon: 'none' })
+							}
+						}).catch(() => {
+							uni.hideLoading()
+						})
+					}
+				}
+			})
+		},
+		// 设为默认
+		setDefault(item) {
+			uni.showLoading({ title: '设置中' })
+			setDefaultAddress({ id: item.id }).then(res => {
+				uni.hideLoading()
+				if (res.code === 1) {
+					uni.showToast({ title: '设置成功', icon: 'success' })
+					this.getList()
+				} else {
+					uni.showToast({ title: res.msg || '设置失败', icon: 'none' })
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.address-list-page {
+	min-height: 100vh;
+	background-color: #f5f5f5;
+	padding: 20upx;
+	padding-bottom: 140upx;
+}
+
+.safe-banner {
+	display: flex;
+	align-items: center;
+	background-color: #f0f9f0;
+	border-radius: 16upx;
+	padding: 20upx 30upx;
+	margin-bottom: 20upx;
+	
+	.safe-icon {
+		margin-right: 20upx;
+	}
+	
+	.safe-text {
+		flex: 1;
+		.title {
+			font-size: 28upx;
+			color: #333;
+			font-weight: bold;
+			margin-bottom: 4upx;
+		}
+		.desc {
+			font-size: 24upx;
+			color: #666;
+		}
+	}
+}
+
+.address-list {
+	.address-item {
+		background-color: #fff;
+		border-radius: 16upx;
+		padding: 30upx;
+		margin-bottom: 20upx;
+		position: relative;
+		
+		&.is-default {
+			border: 2upx solid #07c160;
+			padding-top: 50upx;
+		}
+		
+		.default-badge {
+			position: absolute;
+			top: 0;
+			left: 0;
+			background-color: #07c160;
+			color: #fff;
+			font-size: 22upx;
+			padding: 4upx 16upx;
+			border-radius: 16upx 0 16upx 0;
+		}
+		
+		.info-top {
+			display: flex;
+			align-items: center;
+			margin-bottom: 16upx;
+			
+			.name {
+				font-size: 32upx;
+				font-weight: bold;
+				color: #333;
+				margin-right: 20upx;
+			}
+			
+			.phone {
+				font-size: 28upx;
+				color: #333;
+				margin-right: 20upx;
+			}
+			
+			.tag {
+				font-size: 22upx;
+				padding: 2upx 10upx;
+				border-radius: 6upx;
+				margin-right: 10upx;
+				
+				&.default-tag {
+					background-color: rgba(7, 193, 96, 0.1);
+					color: #07c160;
+				}
+				
+				&.custom-tag {
+					background-color: #f5f5f5;
+					color: #666;
+				}
+			}
+		}
+		
+		.address-detail {
+			font-size: 28upx;
+			color: #666;
+			line-height: 1.5;
+			margin-bottom: 20upx;
+			
+			text {
+				margin-right: 10upx;
+			}
+		}
+		
+		.distance {
+			display: flex;
+			align-items: center;
+			font-size: 24upx;
+			color: #999;
+			margin-bottom: 20upx;
+			
+			text {
+				margin-left: 6upx;
+			}
+		}
+		
+		.action-bar {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			border-top: 1upx solid #eee;
+			padding-top: 20upx;
+			
+			.left-action {
+				.set-default {
+					display: flex;
+					align-items: center;
+					font-size: 26upx;
+					color: #666;
+					
+					.radio-circle {
+						width: 28upx;
+						height: 28upx;
+						border: 2upx solid #ccc;
+						border-radius: 50%;
+						margin-right: 10upx;
+					}
+				}
+			}
+			
+			.right-action {
+				display: flex;
+				flex: 1;
+				justify-content: flex-end;
+				
+				.action-btn {
+					display: flex;
+					align-items: center;
+					font-size: 26upx;
+					color: #666;
+					margin-left: 40upx;
+					
+					text {
+						margin-left: 6upx;
+					}
+				}
+			}
+		}
+	}
+}
+
+.bottom-btn-wrap {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	padding: 20upx 30upx;
+	background-color: #fff;
+	box-shadow: 0 -2upx 10upx rgba(0,0,0,0.05);
+	box-sizing: border-box;
+	z-index: 99;
+	
+	.add-btn {
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 40upx;
+		font-size: 32upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 80upx;
+		
+		.plus {
+			font-size: 40upx;
+			margin-right: 10upx;
+			font-weight: 300;
+		}
+		
+		&::after {
+			border: none;
+		}
+	}
+}
+</style>

+ 6 - 11
mallApp/src/utils/mainIndexSprite.js

@@ -16,7 +16,7 @@ const DEFAULT_SHEET_KEY = "main";
  */
 export const SPRITE_SHEETS = {
   main: {
-    path: "/hhb/sprite_icon/main-index.webp?time=26071614",
+    path: "/hhb/sprite_icon/main-index.webp?time=2607171640",
     width: 500,
     height: 500,
     icons: {
@@ -36,19 +36,14 @@ export const SPRITE_SHEETS = {
       "dianpu-line": { x: 167, y: 114, w: 51, h: 51 },
       "tuichu": { x: 243, y: 111, w: 49, h: 51 },
       "xiaoxi": { x: 8, y: 186, w: 43, h: 44 },
-      "qianbao": { x: 65, y: 180, w: 58, h: 52 },
-      "huoche-da": { x: 140, y: 184, w: 63, h: 53 },
-      "baoguo": { x: 218, y: 180, w: 57, h: 53 },
-      "fanghu-yuan": { x: 299, y: 174, w: 53, h: 61 },
-      "youhuiquan": { x: 382, y: 183, w: 51, h: 49 },
+      "qianbao": { x: 68, y: 182, w: 55, h: 48 },
+      "huoche-da": { x: 142, y: 182, w: 58, h: 48 },
+      "baoguo": { x: 219, y: 182, w: 49, h: 48 },
+      "fanghu-yuan": { x: 282, y: 182, w: 42, h: 48 },
+      "youhuiquan-yellow": { x: 344, y: 184, w: 50, h: 50 },
       "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
       "icon-home": { x: 274, y: 102, w: 52, h: 52 },
       "icon-home-active": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
-      "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
       "gengduo-quan": { x: 274, y: 102, w: 52, h: 52 },
 	  "icon-home": { x: 7, y: 245, w: 50, h: 50 },
 	  "icon-home-active": { x: 60, y: 245, w: 50, h: 50 },