完善下单逻辑及其ui展示,修复支付倒计时显示错误bug
This commit is contained in:
461
.pages-backup/pages.consumer.2026-05-21T04-00-37-252Z.json
Normal file
461
.pages-backup/pages.consumer.2026-05-21T04-00-37-252Z.json
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/main/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "首页",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/boot",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/login",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户登录",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "注册"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/forgot-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "忘记密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/terms",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户协议与隐私政策"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/center",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/change-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/bind-phone",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "绑定手机"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/bind-email",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "绑定邮箱"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/messages",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/cart",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "购物车",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/category",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "分类",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subPackages": [
|
||||||
|
{
|
||||||
|
"root": "pages/mall/consumer",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "settings",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "edit-profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "wallet",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的钱包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "withdraw",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额提现"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "search",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "搜索",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "channel-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "频道详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "shop-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "店铺详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "coupons",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的优惠券"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "favorites",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的收藏"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "footprint",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的足迹"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "收货地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "收银台"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment-success",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "支付成功",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "orders",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的订单",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "logistics",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "物流详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "评价晒单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "退款/售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "apply-refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "申请售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund-review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat_new",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天(新版)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "软件订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订阅详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/subscribe-checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/my-subscriptions",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/followed-shops",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关注店铺"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/signin",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "签到"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分兑换"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange-records",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "兑换记录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "red-packets/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的红包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/add",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "添加银行卡"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "居家上门服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/apply",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "提交服务申请",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/service-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "预约服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务单详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/feedback",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "验收反馈",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/verify",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡验证"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "balance/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "my-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "message-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "member/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "会员中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品评价"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#999999",
|
||||||
|
"selectedColor": "#ff5000",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"borderStyle": "black",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/index",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "static/tabbar/home.png",
|
||||||
|
"selectedIconPath": "static/tabbar/home-active.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/messages",
|
||||||
|
"text": "消息",
|
||||||
|
"iconPath": "static/tabbar/message.png",
|
||||||
|
"selectedIconPath": "static/tabbar/message.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/cart",
|
||||||
|
"text": "购物车",
|
||||||
|
"iconPath": "static/tabbar/cart.png",
|
||||||
|
"selectedIconPath": "static/tabbar/cart.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/profile",
|
||||||
|
"text": "我的",
|
||||||
|
"iconPath": "static/tabbar/user.png",
|
||||||
|
"selectedIconPath": "static/tabbar/user.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "mall",
|
||||||
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"condition": {
|
||||||
|
"current": 0,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "consumer端",
|
||||||
|
"path": "pages/main/index",
|
||||||
|
"query": "role=consumer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1952
.pages-backup/pages.consumer.2026-05-25T01-22-40-895Z.json
Normal file
1952
.pages-backup/pages.consumer.2026-05-25T01-22-40-895Z.json
Normal file
File diff suppressed because it is too large
Load Diff
464
.pages-backup/pages.consumer.2026-05-25T03-16-26-126Z.json
Normal file
464
.pages-backup/pages.consumer.2026-05-25T03-16-26-126Z.json
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/main/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "首页",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/boot",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/login",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户登录",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "注册"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/forgot-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "忘记密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/terms",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户协议与隐私政策"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/center",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/change-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/bind-phone",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "绑定手机"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/bind-email",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "绑定邮箱"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/messages",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息",
|
||||||
|
"enablePullDownRefresh": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/cart",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "购物车",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/main/category",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "分类",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subPackages": [
|
||||||
|
{
|
||||||
|
"root": "pages/mall/consumer",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "settings",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "edit-profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "wallet",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的钱包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "withdraw",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额提现"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "search",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "搜索",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "channel-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "频道详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "shop-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "店铺详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "coupons",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的优惠券"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "favorites",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的收藏"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "footprint",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的足迹"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "收货地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment-success",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "支付成功",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "orders",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "logistics",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "物流详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "评价晒单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "退款/售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "apply-refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "申请售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund-review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat_new",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天(新版)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "软件订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订阅详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/subscribe-checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/my-subscriptions",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/followed-shops",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关注店铺"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/signin",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "签到"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分兑换"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange-records",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "兑换记录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "red-packets/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的红包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/add",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "添加银行卡"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "居家上门服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/apply",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "提交服务申请",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/service-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "预约服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务单详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/feedback",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "验收反馈",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/verify",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡验证"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "balance/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "my-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "message-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "member/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "会员中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品评价"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#999999",
|
||||||
|
"selectedColor": "#ff5000",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"borderStyle": "black",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/index",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "static/tabbar/home.png",
|
||||||
|
"selectedIconPath": "static/tabbar/home-active.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/messages",
|
||||||
|
"text": "消息",
|
||||||
|
"iconPath": "static/tabbar/message.png",
|
||||||
|
"selectedIconPath": "static/tabbar/message.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/cart",
|
||||||
|
"text": "购物车",
|
||||||
|
"iconPath": "static/tabbar/cart.png",
|
||||||
|
"selectedIconPath": "static/tabbar/cart.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/main/profile",
|
||||||
|
"text": "我的",
|
||||||
|
"iconPath": "static/tabbar/user.png",
|
||||||
|
"selectedIconPath": "static/tabbar/user.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "mall",
|
||||||
|
"navigationBarBackgroundColor": "#FFFFFF",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"condition": {
|
||||||
|
"current": 0,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "consumer端",
|
||||||
|
"path": "pages/main/index",
|
||||||
|
"query": "role=consumer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,13 +52,13 @@
|
|||||||
:key="product.id + '-' + pIndex"
|
:key="product.id + '-' + pIndex"
|
||||||
class="hmall-recommend-product"
|
class="hmall-recommend-product"
|
||||||
>
|
>
|
||||||
<image class="hmall-recommend-product-image" :src="getChannelProductImage(product)" mode="aspectFill" />
|
<image class="hmall-recommend-product-image" :src="getChannelProductImage(product)" mode="aspectFill" @error="handleChannelProductImageError(product.id)" />
|
||||||
<text class="hmall-recommend-product-name">{{ product.shortName }}</text>
|
<text class="hmall-recommend-product-name">{{ getChannelProductTitle(product) }}</text>
|
||||||
<view class="hmall-recommend-price-row">
|
<view class="hmall-recommend-price-row">
|
||||||
<text class="hmall-recommend-product-tag" :style="{ color: channel.themeColor }">{{ product.tag }}</text>
|
<text class="hmall-recommend-product-tag" :style="{ color: channel.themeColor }">{{ getChannelProductTag(product) }}</text>
|
||||||
<text class="hmall-recommend-product-price" :style="{ color: channel.themeColor }">¥{{ formatChannelPrice(product.price) }}</text>
|
<text class="hmall-recommend-product-price" :style="{ color: channel.themeColor }">¥{{ formatChannelPrice(getChannelSalePrice(product)) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="product.marketPrice > product.price" class="hmall-recommend-market-price">¥{{ formatChannelPrice(product.marketPrice) }}</text>
|
<text v-if="showChannelMarketPrice(product)" class="hmall-recommend-market-price">¥{{ formatChannelPrice(getChannelMarketPrice(product)) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -167,6 +167,7 @@ import type { Category, Product } from '@/utils/supabaseService.uts'
|
|||||||
import type { MarketingChannel, ChannelProduct, SimpleCategoryChannel } from '@/utils/mockChannelData.uts'
|
import type { MarketingChannel, ChannelProduct, SimpleCategoryChannel } from '@/utils/mockChannelData.uts'
|
||||||
|
|
||||||
const failedProductImageIds = ref<string[]>([])
|
const failedProductImageIds = ref<string[]>([])
|
||||||
|
const failedChannelImageIds = ref<string[]>([])
|
||||||
|
|
||||||
type SecondaryCategoryPage = {
|
type SecondaryCategoryPage = {
|
||||||
id: string
|
id: string
|
||||||
@@ -266,9 +267,50 @@ function getCategoryDisplayIcon(category: Category): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getChannelProductImage(product: ChannelProduct): string {
|
function getChannelProductImage(product: ChannelProduct): string {
|
||||||
|
if (failedChannelImageIds.value.indexOf(product.id) != -1) {
|
||||||
|
return '/static/images/default.png'
|
||||||
|
}
|
||||||
return product.image != '' ? product.image : '/static/images/default.png'
|
return product.image != '' ? product.image : '/static/images/default.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChannelProductImageError(productId: string): void {
|
||||||
|
if (productId == '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (failedChannelImageIds.value.indexOf(productId) == -1) {
|
||||||
|
failedChannelImageIds.value.push(productId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelProductTitle(product: ChannelProduct): string {
|
||||||
|
if (product.shortName != null && product.shortName != '') {
|
||||||
|
return product.shortName
|
||||||
|
}
|
||||||
|
if (product.name != null && product.name != '') {
|
||||||
|
return product.name
|
||||||
|
}
|
||||||
|
return '商品补充中'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelProductTag(product: ChannelProduct): string {
|
||||||
|
if (product.tag != null && product.tag != '') {
|
||||||
|
return product.tag
|
||||||
|
}
|
||||||
|
return '活动价'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelSalePrice(product: ChannelProduct): number {
|
||||||
|
return product.price > 0 ? product.price : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelMarketPrice(product: ChannelProduct): number {
|
||||||
|
return product.marketPrice > 0 ? product.marketPrice : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function showChannelMarketPrice(product: ChannelProduct): boolean {
|
||||||
|
return getChannelMarketPrice(product) > getChannelSalePrice(product)
|
||||||
|
}
|
||||||
|
|
||||||
function formatChannelPrice(price: number): string {
|
function formatChannelPrice(price: number): string {
|
||||||
const rounded = Math.round(price)
|
const rounded = Math.round(price)
|
||||||
if (Math.abs(price - rounded) < 0.001) {
|
if (Math.abs(price - rounded) < 0.001) {
|
||||||
|
|||||||
363
components/mall/GuessYouLike/GuessYouLike.uvue
Normal file
363
components/mall/GuessYouLike/GuessYouLike.uvue
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
<template>
|
||||||
|
<view class="guess-you-like-section">
|
||||||
|
<view v-if="showTitle" class="guess-header">
|
||||||
|
<text class="guess-title">{{ titleText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="products.length > 0" class="guess-grid">
|
||||||
|
<view
|
||||||
|
v-for="product in products"
|
||||||
|
:key="product.id"
|
||||||
|
class="guess-card"
|
||||||
|
@click="handleProductClick(product.id)"
|
||||||
|
>
|
||||||
|
<image class="guess-image" :src="product.image" mode="aspectFill" />
|
||||||
|
<view class="guess-body">
|
||||||
|
<text class="guess-name" :lines="2">{{ product.name }}</text>
|
||||||
|
<view class="guess-price-row">
|
||||||
|
<text class="guess-price">¥{{ product.price.toFixed(2) }}</text>
|
||||||
|
<text v-if="product.originalPrice > product.price" class="guess-original-price">¥{{ product.originalPrice.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-if="product.salesText !== ''" class="guess-sales">{{ product.salesText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="!loading && errorText === ''" class="guess-empty">
|
||||||
|
<text class="guess-empty-text">暂无推荐商品</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading" class="guess-status-row">
|
||||||
|
<text class="guess-status-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="errorText !== ''" class="guess-status-row">
|
||||||
|
<text class="guess-status-text">{{ errorText }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="finished && products.length > 0" class="guess-status-row">
|
||||||
|
<text class="guess-status-text">没有更多了</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||||
|
|
||||||
|
type RecommendProduct = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
image: string
|
||||||
|
price: number
|
||||||
|
originalPrice: number
|
||||||
|
salesText: string
|
||||||
|
categoryId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_IMAGE = '/static/logo.png'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '猜你喜欢'
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 8
|
||||||
|
},
|
||||||
|
categoryId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
excludeProductIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
loadMoreKey: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
showTitle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'productClick', productId: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const products = ref<Array<RecommendProduct>>([])
|
||||||
|
const page = ref<number>(1)
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const finished = ref<boolean>(false)
|
||||||
|
const errorText = ref<string>('')
|
||||||
|
|
||||||
|
const titleText = computed((): string => {
|
||||||
|
return props.title as string
|
||||||
|
})
|
||||||
|
|
||||||
|
const toExcludeProductIds = (): Array<string> => {
|
||||||
|
const result: Array<string> = []
|
||||||
|
try {
|
||||||
|
const normalized = JSON.parse(JSON.stringify(props.excludeProductIds))
|
||||||
|
if (Array.isArray(normalized)) {
|
||||||
|
for (let i = 0; i < normalized.length; i++) {
|
||||||
|
const item = normalized[i]
|
||||||
|
if (item != null) {
|
||||||
|
const itemText = '' + item
|
||||||
|
if (itemText !== '' && result.indexOf(itemText) < 0) {
|
||||||
|
result.push(itemText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNumberField = (obj: UTSJSONObject, key: string): number => {
|
||||||
|
try {
|
||||||
|
const value = obj.getNumber(key)
|
||||||
|
if (value != null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStringField = (obj: UTSJSONObject, key: string): string => {
|
||||||
|
try {
|
||||||
|
const value = obj.getString(key)
|
||||||
|
if (value != null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatSalesText = (salesCount: number): string => {
|
||||||
|
if (salesCount <= 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (salesCount >= 10000) {
|
||||||
|
const value = Math.floor(salesCount / 100) / 100
|
||||||
|
return '已售 ' + value + '万'
|
||||||
|
}
|
||||||
|
return '已售 ' + salesCount
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseProducts = (rows: Array<UTSJSONObject>): Array<RecommendProduct> => {
|
||||||
|
const result: Array<RecommendProduct> = []
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const row = rows[i]
|
||||||
|
const productId = getStringField(row, 'id')
|
||||||
|
if (productId === '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const price = getNumberField(row, 'base_price') > 0 ? getNumberField(row, 'base_price') : getNumberField(row, 'price')
|
||||||
|
const originalPrice = getNumberField(row, 'market_price') > 0 ? getNumberField(row, 'market_price') : getNumberField(row, 'original_price')
|
||||||
|
const image = getStringField(row, 'main_image_url') !== ''
|
||||||
|
? getStringField(row, 'main_image_url')
|
||||||
|
: (getStringField(row, 'image_url') !== '' ? getStringField(row, 'image_url') : DEFAULT_IMAGE)
|
||||||
|
const salesCount = getNumberField(row, 'sale_count')
|
||||||
|
const name = getStringField(row, 'name') !== '' ? getStringField(row, 'name') : '精选商品'
|
||||||
|
result.push({
|
||||||
|
id: productId,
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
price: price > 0 ? price : 0,
|
||||||
|
originalPrice: originalPrice > 0 ? originalPrice : 0,
|
||||||
|
salesText: formatSalesText(salesCount),
|
||||||
|
categoryId: getStringField(row, 'category_id')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadProducts = async (reset: boolean): Promise<void> => {
|
||||||
|
if (loading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!reset && finished.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
products.value = []
|
||||||
|
page.value = 1
|
||||||
|
finished.value = false
|
||||||
|
errorText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const requestParams = new UTSJSONObject()
|
||||||
|
requestParams.set('limit', props.pageSize as number)
|
||||||
|
requestParams.set('offset', (page.value - 1) * (props.pageSize as number))
|
||||||
|
requestParams.set('categoryId', props.categoryId as string)
|
||||||
|
requestParams.set('source', props.source as string)
|
||||||
|
requestParams.set('excludeProductIds', toExcludeProductIds())
|
||||||
|
|
||||||
|
const rows = await supabaseService.getRecommendProducts(requestParams)
|
||||||
|
const parsed = parseProducts(rows)
|
||||||
|
if (reset) {
|
||||||
|
products.value = parsed
|
||||||
|
} else {
|
||||||
|
const merged = products.value.slice()
|
||||||
|
for (let i = 0; i < parsed.length; i++) {
|
||||||
|
let exists = false
|
||||||
|
for (let j = 0; j < merged.length; j++) {
|
||||||
|
if (merged[j].id === parsed[i].id) {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
merged.push(parsed[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
products.value = merged
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.length < (props.pageSize as number)) {
|
||||||
|
finished.value = true
|
||||||
|
} else {
|
||||||
|
page.value = page.value + 1
|
||||||
|
}
|
||||||
|
if (parsed.length === 0 && products.value.length === 0) {
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载猜你喜欢失败:', error)
|
||||||
|
errorText.value = '推荐商品加载失败'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleProductClick = (productId: string): void => {
|
||||||
|
emit('productClick', productId)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.loadMoreKey, (newValue: number, oldValue: number) => {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
loadProducts(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.categoryId, (newValue: string, oldValue: string) => {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
loadProducts(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.excludeProductIds), (newValue: string, oldValue: string) => {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
loadProducts(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadProducts(true)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.guess-you-like-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-header {
|
||||||
|
padding: 0 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-card {
|
||||||
|
width: 48.5%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 148px;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-body {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-name {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 19px;
|
||||||
|
color: #222222;
|
||||||
|
height: 38px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-price-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-price {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-original-price {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999999;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-sales {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999999;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-empty,
|
||||||
|
.guess-status-row {
|
||||||
|
padding: 14px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guess-empty-text,
|
||||||
|
.guess-status-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
114
mall_sql/migrations/20260522_hss_service_catalog.sql
Normal file
114
mall_sql/migrations/20260522_hss_service_catalog.sql
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_catalog (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
category TEXT NOT NULL DEFAULT '',
|
||||||
|
price NUMERIC(10, 2) NOT NULL DEFAULT 0,
|
||||||
|
duration_text TEXT NOT NULL DEFAULT '',
|
||||||
|
summary TEXT NOT NULL DEFAULT '',
|
||||||
|
tags_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
suitable_for TEXT NOT NULL DEFAULT '',
|
||||||
|
sort_no INTEGER NOT NULL DEFAULT 0,
|
||||||
|
status SMALLINT NOT NULL DEFAULT 1,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_catalog_status_sort
|
||||||
|
ON public.hss_service_catalog(status, sort_no)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_catalog_category
|
||||||
|
ON public.hss_service_catalog(category)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.tg_hss_service_catalog_updated_at()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = now();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_hss_service_catalog_updated_at ON public.hss_service_catalog;
|
||||||
|
CREATE TRIGGER trg_hss_service_catalog_updated_at
|
||||||
|
BEFORE UPDATE ON public.hss_service_catalog
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.tg_hss_service_catalog_updated_at();
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_catalog ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_catalog_public_select ON public.hss_service_catalog;
|
||||||
|
CREATE POLICY hss_service_catalog_public_select
|
||||||
|
ON public.hss_service_catalog
|
||||||
|
FOR SELECT
|
||||||
|
USING (deleted_at IS NULL AND status = 1);
|
||||||
|
|
||||||
|
INSERT INTO public.hss_service_catalog (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
category,
|
||||||
|
price,
|
||||||
|
duration_text,
|
||||||
|
summary,
|
||||||
|
tags_json,
|
||||||
|
suitable_for,
|
||||||
|
sort_no,
|
||||||
|
status
|
||||||
|
) VALUES
|
||||||
|
(
|
||||||
|
'svc-001',
|
||||||
|
'基础上门护理',
|
||||||
|
'日常照护',
|
||||||
|
168,
|
||||||
|
'约 2 小时',
|
||||||
|
'覆盖生命体征监测、基础照护、风险提醒。',
|
||||||
|
'["适老化", "护理员上门", "支持家属陪同"]'::jsonb,
|
||||||
|
'行动不便、术后恢复、慢病随访老人',
|
||||||
|
10,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'svc-002',
|
||||||
|
'康复训练指导',
|
||||||
|
'康复支持',
|
||||||
|
260,
|
||||||
|
'约 3 小时',
|
||||||
|
'提供肢体训练、步态练习和居家康复建议。',
|
||||||
|
'["康复师", "步骤清晰", "可连续预约"]'::jsonb,
|
||||||
|
'卒中恢复、术后康复、失能半失能老人',
|
||||||
|
20,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'svc-003',
|
||||||
|
'慢病健康随访',
|
||||||
|
'健康管理',
|
||||||
|
128,
|
||||||
|
'约 90 分钟',
|
||||||
|
'完成血压血糖监测、用药核对与健康宣教。',
|
||||||
|
'["随访", "慢病", "可生成记录"]'::jsonb,
|
||||||
|
'高血压、糖尿病等长期管理老人',
|
||||||
|
30,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
category = EXCLUDED.category,
|
||||||
|
price = EXCLUDED.price,
|
||||||
|
duration_text = EXCLUDED.duration_text,
|
||||||
|
summary = EXCLUDED.summary,
|
||||||
|
tags_json = EXCLUDED.tags_json,
|
||||||
|
suitable_for = EXCLUDED.suitable_for,
|
||||||
|
sort_no = EXCLUDED.sort_no,
|
||||||
|
status = EXCLUDED.status,
|
||||||
|
deleted_at = NULL,
|
||||||
|
updated_at = now();
|
||||||
|
|
||||||
|
COMMENT ON TABLE public.hss_service_catalog IS '居家上门服务目录表';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
274
mall_sql/migrations/20260522_hss_service_mvp_p0.sql
Normal file
274
mall_sql/migrations/20260522_hss_service_mvp_p0.sql
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_user_addresses
|
||||||
|
ADD COLUMN IF NOT EXISTS latitude DOUBLE PRECISION,
|
||||||
|
ADD COLUMN IF NOT EXISTS longitude DOUBLE PRECISION,
|
||||||
|
ADD COLUMN IF NOT EXISTS coordinate_type TEXT NOT NULL DEFAULT 'gcj02';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_orders (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_no TEXT NOT NULL UNIQUE,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
service_id TEXT NOT NULL,
|
||||||
|
service_name TEXT NOT NULL,
|
||||||
|
service_snapshot_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
service_address_id UUID,
|
||||||
|
address_snapshot_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
recipient_name TEXT NOT NULL DEFAULT '',
|
||||||
|
recipient_phone TEXT NOT NULL DEFAULT '',
|
||||||
|
contact_name TEXT NOT NULL DEFAULT '',
|
||||||
|
contact_phone TEXT NOT NULL DEFAULT '',
|
||||||
|
appointment_time TIMESTAMPTZ,
|
||||||
|
remark TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'created',
|
||||||
|
current_assignment_id TEXT NOT NULL DEFAULT '',
|
||||||
|
current_staff_id UUID,
|
||||||
|
accepted_at TIMESTAMPTZ,
|
||||||
|
departed_at TIMESTAMPTZ,
|
||||||
|
arrived_at TIMESTAMPTZ,
|
||||||
|
service_started_at TIMESTAMPTZ,
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
pending_acceptance_at TIMESTAMPTZ,
|
||||||
|
accepted_by_user_at TIMESTAMPTZ,
|
||||||
|
reviewed_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
deleted_by UUID,
|
||||||
|
CONSTRAINT chk_hss_service_orders_status CHECK (
|
||||||
|
status IN (
|
||||||
|
'created', 'paid', 'assigned', 'accepted', 'rejected', 'departed', 'arrived',
|
||||||
|
'in_service', 'completed', 'pending_acceptance', 'accepted_by_user',
|
||||||
|
'reviewed', 'settled', 'cancelled', 'exception'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_assignments (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_id TEXT NOT NULL REFERENCES public.hss_service_orders(id) ON DELETE CASCADE,
|
||||||
|
staff_id UUID NOT NULL REFERENCES public.ml_delivery_staff(id) ON DELETE RESTRICT,
|
||||||
|
station_id UUID REFERENCES public.ml_delivery_stations(id) ON DELETE SET NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'assigned',
|
||||||
|
assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
accepted_at TIMESTAMPTZ,
|
||||||
|
rejected_at TIMESTAMPTZ,
|
||||||
|
reject_reason TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT chk_hss_service_assignments_status CHECK (
|
||||||
|
status IN ('assigned', 'accepted', 'rejected', 'departed', 'arrived', 'in_service', 'pending_acceptance', 'completed', 'cancelled', 'exception')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_execution_records (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_id TEXT NOT NULL REFERENCES public.hss_service_orders(id) ON DELETE CASCADE,
|
||||||
|
assignment_id TEXT NOT NULL REFERENCES public.hss_service_assignments(id) ON DELETE CASCADE,
|
||||||
|
checkin_time TIMESTAMPTZ,
|
||||||
|
checkin_latitude DOUBLE PRECISION,
|
||||||
|
checkin_longitude DOUBLE PRECISION,
|
||||||
|
checkin_address TEXT NOT NULL DEFAULT '',
|
||||||
|
service_started_at TIMESTAMPTZ,
|
||||||
|
service_finished_at TIMESTAMPTZ,
|
||||||
|
actual_duration_minutes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
service_items_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
summary TEXT NOT NULL DEFAULT '',
|
||||||
|
remark TEXT NOT NULL DEFAULT '',
|
||||||
|
track_points_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_evidence_files (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_id TEXT NOT NULL REFERENCES public.hss_service_orders(id) ON DELETE CASCADE,
|
||||||
|
execution_record_id TEXT REFERENCES public.hss_service_execution_records(id) ON DELETE CASCADE,
|
||||||
|
phase TEXT NOT NULL DEFAULT 'service',
|
||||||
|
file_type TEXT NOT NULL DEFAULT 'image',
|
||||||
|
storage_path TEXT NOT NULL DEFAULT '',
|
||||||
|
file_url TEXT NOT NULL DEFAULT '',
|
||||||
|
latitude DOUBLE PRECISION,
|
||||||
|
longitude DOUBLE PRECISION,
|
||||||
|
captured_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_order_status_logs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_id TEXT NOT NULL REFERENCES public.hss_service_orders(id) ON DELETE CASCADE,
|
||||||
|
from_status TEXT NOT NULL DEFAULT '',
|
||||||
|
to_status TEXT NOT NULL,
|
||||||
|
operator_id UUID,
|
||||||
|
operator_role TEXT NOT NULL DEFAULT '',
|
||||||
|
remark TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.hss_service_reviews (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
order_id TEXT NOT NULL REFERENCES public.hss_service_orders(id) ON DELETE CASCADE,
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
rating INTEGER NOT NULL DEFAULT 5,
|
||||||
|
tags_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_orders_user_status
|
||||||
|
ON public.hss_service_orders(user_id, status)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_orders_staff_status
|
||||||
|
ON public.hss_service_orders(current_staff_id, status)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_assignments_staff_status
|
||||||
|
ON public.hss_service_assignments(staff_id, status);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_execution_records_order
|
||||||
|
ON public.hss_service_execution_records(order_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_evidence_files_order
|
||||||
|
ON public.hss_service_evidence_files(order_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_logs_order_created
|
||||||
|
ON public.hss_service_order_status_logs(order_id, created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_reviews_order
|
||||||
|
ON public.hss_service_reviews(order_id);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.tg_hss_set_updated_at()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = now();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_hss_service_orders_updated_at ON public.hss_service_orders;
|
||||||
|
CREATE TRIGGER trg_hss_service_orders_updated_at
|
||||||
|
BEFORE UPDATE ON public.hss_service_orders
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.tg_hss_set_updated_at();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_hss_service_assignments_updated_at ON public.hss_service_assignments;
|
||||||
|
CREATE TRIGGER trg_hss_service_assignments_updated_at
|
||||||
|
BEFORE UPDATE ON public.hss_service_assignments
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.tg_hss_set_updated_at();
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trg_hss_service_execution_records_updated_at ON public.hss_service_execution_records;
|
||||||
|
CREATE TRIGGER trg_hss_service_execution_records_updated_at
|
||||||
|
BEFORE UPDATE ON public.hss_service_execution_records
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION public.tg_hss_set_updated_at();
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE public.hss_service_assignments ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE public.hss_service_execution_records ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE public.hss_service_evidence_files ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE public.hss_service_order_status_logs ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE public.hss_service_reviews ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_orders_user_select ON public.hss_service_orders;
|
||||||
|
CREATE POLICY hss_service_orders_user_select
|
||||||
|
ON public.hss_service_orders
|
||||||
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
deleted_at IS NULL AND (
|
||||||
|
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
|
||||||
|
OR current_staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_orders_user_insert ON public.hss_service_orders;
|
||||||
|
CREATE POLICY hss_service_orders_user_insert
|
||||||
|
ON public.hss_service_orders
|
||||||
|
FOR INSERT
|
||||||
|
TO authenticated
|
||||||
|
WITH CHECK (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_orders_user_update ON public.hss_service_orders;
|
||||||
|
CREATE POLICY hss_service_orders_user_update
|
||||||
|
ON public.hss_service_orders
|
||||||
|
FOR UPDATE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
deleted_at IS NULL AND (
|
||||||
|
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
|
||||||
|
OR current_staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WITH CHECK (
|
||||||
|
deleted_at IS NULL AND (
|
||||||
|
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
|
||||||
|
OR current_staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_assignments_staff_select ON public.hss_service_assignments;
|
||||||
|
CREATE POLICY hss_service_assignments_staff_select
|
||||||
|
ON public.hss_service_assignments
|
||||||
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_assignments_staff_update ON public.hss_service_assignments;
|
||||||
|
CREATE POLICY hss_service_assignments_staff_update
|
||||||
|
ON public.hss_service_assignments
|
||||||
|
FOR UPDATE
|
||||||
|
TO authenticated
|
||||||
|
USING (staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL))
|
||||||
|
WITH CHECK (staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_assignments_staff_insert ON public.hss_service_assignments;
|
||||||
|
CREATE POLICY hss_service_assignments_staff_insert
|
||||||
|
ON public.hss_service_assignments
|
||||||
|
FOR INSERT
|
||||||
|
TO authenticated
|
||||||
|
WITH CHECK (staff_id IN (SELECT id FROM public.ml_delivery_staff WHERE uid IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()) AND deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_execution_records_order_access ON public.hss_service_execution_records;
|
||||||
|
CREATE POLICY hss_service_execution_records_order_access
|
||||||
|
ON public.hss_service_execution_records
|
||||||
|
FOR ALL
|
||||||
|
TO authenticated
|
||||||
|
USING (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL))
|
||||||
|
WITH CHECK (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_evidence_files_order_access ON public.hss_service_evidence_files;
|
||||||
|
CREATE POLICY hss_service_evidence_files_order_access
|
||||||
|
ON public.hss_service_evidence_files
|
||||||
|
FOR ALL
|
||||||
|
TO authenticated
|
||||||
|
USING (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL))
|
||||||
|
WITH CHECK (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_order_status_logs_order_access ON public.hss_service_order_status_logs;
|
||||||
|
CREATE POLICY hss_service_order_status_logs_order_access
|
||||||
|
ON public.hss_service_order_status_logs
|
||||||
|
FOR ALL
|
||||||
|
TO authenticated
|
||||||
|
USING (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL))
|
||||||
|
WITH CHECK (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL));
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS hss_service_reviews_order_access ON public.hss_service_reviews;
|
||||||
|
CREATE POLICY hss_service_reviews_order_access
|
||||||
|
ON public.hss_service_reviews
|
||||||
|
FOR ALL
|
||||||
|
TO authenticated
|
||||||
|
USING (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL))
|
||||||
|
WITH CHECK (order_id IN (SELECT id FROM public.hss_service_orders WHERE deleted_at IS NULL));
|
||||||
|
|
||||||
|
COMMENT ON TABLE public.hss_service_orders IS '居家上门服务订单主表';
|
||||||
|
COMMENT ON TABLE public.hss_service_assignments IS '居家上门服务派单表';
|
||||||
|
COMMENT ON TABLE public.hss_service_execution_records IS '居家上门服务执行记录表';
|
||||||
|
COMMENT ON TABLE public.hss_service_evidence_files IS '居家上门服务证据文件表';
|
||||||
|
COMMENT ON TABLE public.hss_service_order_status_logs IS '居家上门服务状态日志表';
|
||||||
|
COMMENT ON TABLE public.hss_service_reviews IS '居家上门服务评价表';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
17
mall_sql/migrations/20260525_consumer_order_soft_delete.sql
Normal file
17
mall_sql/migrations/20260525_consumer_order_soft_delete.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMPTZ NULL,
|
||||||
|
ADD COLUMN IF NOT EXISTS consumer_deleted_at TIMESTAMPTZ NULL;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.ml_orders.cancelled_at IS '订单取消时间,包含用户取消与支付超时取消';
|
||||||
|
COMMENT ON COLUMN public.ml_orders.consumer_deleted_at IS '消费者侧软删除时间,仅影响消费者订单列表展示';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ml_orders_consumer_deleted_at
|
||||||
|
ON public.ml_orders(user_id, consumer_deleted_at, created_at DESC);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ml_orders_consumer_visible
|
||||||
|
ON public.ml_orders(user_id, created_at DESC)
|
||||||
|
WHERE consumer_deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS payment_status SMALLINT NOT NULL DEFAULT 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS pay_expire_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS cancel_reason TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE public.hss_service_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS consumer_deleted_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_hss_service_orders_user_visible
|
||||||
|
ON public.hss_service_orders(user_id, created_at DESC)
|
||||||
|
WHERE consumer_deleted_at IS NULL;
|
||||||
|
|
||||||
|
UPDATE public.hss_service_orders
|
||||||
|
SET pay_expire_at = created_at + INTERVAL '10 minutes'
|
||||||
|
WHERE pay_expire_at IS NULL
|
||||||
|
AND status = 'created'
|
||||||
|
AND payment_status = 1;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS pay_expire_at TIMESTAMPTZ,
|
||||||
|
ADD COLUMN IF NOT EXISTS payment_status SMALLINT NOT NULL DEFAULT 1,
|
||||||
|
ADD COLUMN IF NOT EXISTS cancel_reason TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMPTZ,
|
||||||
|
ADD COLUMN IF NOT EXISTS consumer_deleted_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
77
mall_sql/migrations/20260525_order_timeout_status.sql
Normal file
77
mall_sql/migrations/20260525_order_timeout_status.sql
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS pay_expire_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_ml_order_status;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_ml_payment_status;
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
ADD CONSTRAINT chk_ml_order_status CHECK (order_status IN (1,2,3,4,5,6,7,8));
|
||||||
|
|
||||||
|
ALTER TABLE public.ml_orders
|
||||||
|
ADD CONSTRAINT chk_ml_payment_status CHECK (payment_status IN (1,2,3,4,5));
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.ml_orders.pay_expire_at IS '支付截止时间,超过后订单进入已超时';
|
||||||
|
|
||||||
|
UPDATE public.ml_orders
|
||||||
|
SET pay_expire_at = created_at + INTERVAL '10 minutes'
|
||||||
|
WHERE pay_expire_at IS NULL
|
||||||
|
AND order_status = 1
|
||||||
|
AND payment_status = 1;
|
||||||
|
|
||||||
|
UPDATE public.ml_orders
|
||||||
|
SET order_status = 8,
|
||||||
|
payment_status = 5,
|
||||||
|
cancel_reason = CASE
|
||||||
|
WHEN cancel_reason IS NULL OR cancel_reason = '' THEN '支付超时自动关闭'
|
||||||
|
ELSE cancel_reason
|
||||||
|
END,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE order_status = 1
|
||||||
|
AND payment_status = 1
|
||||||
|
AND pay_expire_at IS NOT NULL
|
||||||
|
AND pay_expire_at <= NOW();
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ml_orders_pay_expire_at
|
||||||
|
ON public.ml_orders(pay_expire_at);
|
||||||
|
|
||||||
|
DROP VIEW IF EXISTS public.ml_orders_detail_view;
|
||||||
|
|
||||||
|
CREATE VIEW public.ml_orders_detail_view AS
|
||||||
|
SELECT
|
||||||
|
o.*,
|
||||||
|
u.username as customer_name,
|
||||||
|
u.phone as customer_phone,
|
||||||
|
m.username as merchant_name,
|
||||||
|
s.shop_name,
|
||||||
|
CASE
|
||||||
|
WHEN o.order_status = 1 THEN '待付款'
|
||||||
|
WHEN o.order_status = 2 THEN '待发货'
|
||||||
|
WHEN o.order_status = 3 THEN '待收货'
|
||||||
|
WHEN o.order_status = 4 THEN '已完成'
|
||||||
|
WHEN o.order_status = 5 THEN '已取消'
|
||||||
|
WHEN o.order_status = 6 THEN '退款中'
|
||||||
|
WHEN o.order_status = 7 THEN '已退款'
|
||||||
|
WHEN o.order_status = 8 THEN '已超时'
|
||||||
|
ELSE '未知'
|
||||||
|
END as order_status_name,
|
||||||
|
CASE
|
||||||
|
WHEN o.payment_status = 1 THEN '未付款'
|
||||||
|
WHEN o.payment_status = 2 THEN '已付款'
|
||||||
|
WHEN o.payment_status = 3 THEN '部分退款'
|
||||||
|
WHEN o.payment_status = 4 THEN '全额退款'
|
||||||
|
WHEN o.payment_status = 5 THEN '已关闭'
|
||||||
|
ELSE '未知'
|
||||||
|
END as payment_status_name
|
||||||
|
FROM public.ml_orders o
|
||||||
|
LEFT JOIN public.ak_users u ON o.user_id = u.id
|
||||||
|
LEFT JOIN public.ak_users m ON o.merchant_id = m.id
|
||||||
|
LEFT JOIN public.ml_shops s ON o.merchant_id = s.merchant_id;
|
||||||
|
|
||||||
|
COMMENT ON VIEW public.ml_orders_detail_view IS '订单详情视图';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -47,7 +47,13 @@
|
|||||||
"optimization": {
|
"optimization": {
|
||||||
"subPackages": true
|
"subPackages": true
|
||||||
},
|
},
|
||||||
"lazyCodeLoading": "requiredComponents"
|
"lazyCodeLoading": "requiredComponents",
|
||||||
|
"requiredPrivateInfos": ["getLocation", "chooseLocation"],
|
||||||
|
"permission": {
|
||||||
|
"scope.userLocation": {
|
||||||
|
"desc": "用于获取您的服务地址,方便服务人员上门服务"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mp-alipay": {
|
"mp-alipay": {
|
||||||
"appid": "",
|
"appid": "",
|
||||||
|
|||||||
@@ -198,7 +198,8 @@
|
|||||||
{
|
{
|
||||||
"path": "payment",
|
"path": "payment",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "收银台"
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -211,8 +212,10 @@
|
|||||||
{
|
{
|
||||||
"path": "orders",
|
"path": "orders",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的订单",
|
"navigationBarTitleText": "",
|
||||||
"enablePullDownRefresh": true
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"path": "pages/user/boot",
|
"path": "pages/user/boot",
|
||||||
@@ -13,6 +13,24 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/address/address-edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/address/address-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "选择服务地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/address/address-map-select",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "地图选点"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/mall/admin/homePage/index",
|
"path": "pages/mall/admin/homePage/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -338,7 +356,8 @@
|
|||||||
{
|
{
|
||||||
"path": "payment",
|
"path": "payment",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "收银台"
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -351,8 +370,10 @@
|
|||||||
{
|
{
|
||||||
"path": "orders",
|
"path": "orders",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的订单",
|
"navigationBarTitleText": "",
|
||||||
"enablePullDownRefresh": true
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
322
pages.json
322
pages.json
@@ -71,13 +71,321 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subPackages": [],
|
"subPackages": [
|
||||||
"globalStyle": {
|
{
|
||||||
"navigationBarTextStyle": "black",
|
"root": "pages/mall/consumer",
|
||||||
"navigationBarTitleText": "delivery",
|
"pages": [
|
||||||
"navigationBarBackgroundColor": "#FFFFFF",
|
{
|
||||||
"backgroundColor": "#F3F7F9"
|
"path": "settings",
|
||||||
},
|
"style": {
|
||||||
|
"navigationBarTitleText": "设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "edit-profile",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "wallet",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的钱包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "withdraw",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额提现"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "search",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "搜索",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "channel-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "频道详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "shop-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "店铺详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "coupons",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的优惠券"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "favorites",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的收藏"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "footprint",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的足迹"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "收货地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "address-edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑地址"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "payment-success",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "支付成功",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "orders",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundColor": "#f5f5f5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "logistics",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "物流详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "评价晒单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "退款/售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "apply-refund",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "申请售后"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "refund-review",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "chat_new",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "客服聊天(新版)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "软件订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/plan-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订阅详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/subscribe-checkout",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "确认订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/my-subscriptions",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "subscription/followed-shops",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关注店铺"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/signin",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "签到"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "积分兑换"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "points/exchange-records",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "兑换记录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "red-packets/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的红包"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡管理"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/add",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "添加银行卡"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "居家上门服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/apply",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "提交服务申请",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/service-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "预约服务",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/order-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "服务单详情",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "home-service/feedback",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "验收反馈",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "bank-cards/verify",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "银行卡验证"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "balance/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "余额"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "my-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "message-detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息详情"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "member/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "会员中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "product-reviews",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品评价"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"color": "#6B7280",
|
"color": "#6B7280",
|
||||||
"selectedColor": "#0F766E",
|
"selectedColor": "#0F766E",
|
||||||
|
|||||||
636
pages/address/address-edit.uvue
Normal file
636
pages/address/address-edit.uvue
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
<template>
|
||||||
|
<view class="address-page">
|
||||||
|
<scroll-view class="address-scroll" scroll-y="true">
|
||||||
|
<view class="address-content">
|
||||||
|
<view class="current-location-card">
|
||||||
|
<text class="section-title">当前选择的位置</text>
|
||||||
|
<text v-if="hasLocation()" class="current-location-name">{{ displayLocationTitle }}</text>
|
||||||
|
<text v-if="hasLocation()" class="current-location-detail">{{ displayLocationDetail }}</text>
|
||||||
|
<text v-else class="current-location-placeholder">请选择小区、医院、养老院或街道位置</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-card">
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">联系人</text>
|
||||||
|
<input v-model="form.contactName" class="form-input" placeholder="请输入联系人姓名" />
|
||||||
|
</view>
|
||||||
|
<view class="form-item form-item-border">
|
||||||
|
<text class="form-label">手机号</text>
|
||||||
|
<input v-model="form.contactPhone" class="form-input" type="number" maxlength="11" placeholder="请输入联系电话" />
|
||||||
|
</view>
|
||||||
|
<view class="form-item form-item-border form-item-tappable" @click="chooseServiceLocation">
|
||||||
|
<view class="form-item-main">
|
||||||
|
<text class="form-label">所在位置</text>
|
||||||
|
<view v-if="hasLocation()" class="location-block">
|
||||||
|
<text class="location-title">{{ displayLocationTitle }}</text>
|
||||||
|
<text class="location-detail">{{ displayLocationDetail }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-else class="location-placeholder">请选择小区/医院/养老院/街道</text>
|
||||||
|
</view>
|
||||||
|
<text class="form-action">重新选择</text>
|
||||||
|
</view>
|
||||||
|
<view class="map-entry-row" @click="goToMapSelect">
|
||||||
|
<text class="map-entry-text">自定义地图选点</text>
|
||||||
|
<text class="map-entry-arrow">></text>
|
||||||
|
</view>
|
||||||
|
<view class="form-item form-item-border">
|
||||||
|
<text class="form-label">详细门牌号</text>
|
||||||
|
<input v-model="form.houseNumber" class="form-input" placeholder="例如 3栋2单元1201 / 住院部3楼" />
|
||||||
|
</view>
|
||||||
|
<view class="form-item form-item-vertical">
|
||||||
|
<text class="form-label">服务备注</text>
|
||||||
|
<textarea v-model="form.remark" class="form-textarea" placeholder="例如 老人行动不便,请提前电话联系" maxlength="120"></textarea>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="bottom-bar">
|
||||||
|
<button class="save-btn" @click="saveAddress">保存服务地址</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { computed, reactive } from 'vue'
|
||||||
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
|
import { HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||||||
|
|
||||||
|
const SELECTED_KEY = 'hss_selected_service_address'
|
||||||
|
const LIST_KEY = 'hss_service_address_list'
|
||||||
|
const MAP_DRAFT_KEY = 'hss_service_address_map_draft'
|
||||||
|
|
||||||
|
type ServiceAddressFormType = {
|
||||||
|
addressId: string
|
||||||
|
userId: string
|
||||||
|
isDefault: boolean
|
||||||
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
phone: string
|
||||||
|
addressName: string
|
||||||
|
locationName: string
|
||||||
|
addressDetail: string
|
||||||
|
locationAddress: string
|
||||||
|
houseNumber: string
|
||||||
|
doorNo: string
|
||||||
|
fullAddress: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
remark: string
|
||||||
|
coordinateType: string
|
||||||
|
createdAt: number
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDefaultForm(): ServiceAddressFormType {
|
||||||
|
const now = Date.now()
|
||||||
|
const storedUserId = uni.getStorageSync('user_id') as string | null
|
||||||
|
return {
|
||||||
|
addressId: 'local-address-' + now,
|
||||||
|
userId: storedUserId != null ? storedUserId : '',
|
||||||
|
isDefault: true,
|
||||||
|
contactName: '',
|
||||||
|
contactPhone: '',
|
||||||
|
phone: '',
|
||||||
|
addressName: '',
|
||||||
|
locationName: '',
|
||||||
|
addressDetail: '',
|
||||||
|
locationAddress: '',
|
||||||
|
houseNumber: '',
|
||||||
|
doorNo: '',
|
||||||
|
fullAddress: '',
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
remark: '',
|
||||||
|
coordinateType: 'gcj02',
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = reactive(createDefaultForm())
|
||||||
|
let editingAddressId = ''
|
||||||
|
|
||||||
|
const displayLocationTitle = computed((): string => {
|
||||||
|
if (form.locationName != '') {
|
||||||
|
return form.locationName
|
||||||
|
}
|
||||||
|
if (form.addressName != '') {
|
||||||
|
return form.addressName
|
||||||
|
}
|
||||||
|
if (form.locationAddress != '') {
|
||||||
|
return form.locationAddress
|
||||||
|
}
|
||||||
|
return '未选择位置'
|
||||||
|
})
|
||||||
|
|
||||||
|
const displayLocationDetail = computed((): string => {
|
||||||
|
if (form.locationAddress != '') {
|
||||||
|
return form.locationAddress
|
||||||
|
}
|
||||||
|
if (form.addressDetail != '') {
|
||||||
|
return form.addressDetail
|
||||||
|
}
|
||||||
|
return '请先通过地图选点获取位置'
|
||||||
|
})
|
||||||
|
|
||||||
|
function hasLocation(): boolean {
|
||||||
|
return form.locationName != '' || form.addressName != '' || form.locationAddress != '' || form.addressDetail != ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAddress(raw: HomeServiceSelectedAddressType): HomeServiceSelectedAddressType {
|
||||||
|
const phoneText = raw.phone != null && raw.phone != '' ? raw.phone : (raw.contactPhone != null ? raw.contactPhone : '')
|
||||||
|
const locationName = raw.locationName != null && raw.locationName != '' ? raw.locationName : (raw.addressName != null ? raw.addressName : '')
|
||||||
|
const locationAddress = raw.locationAddress != null && raw.locationAddress != '' ? raw.locationAddress : (raw.addressDetail != null ? raw.addressDetail : '')
|
||||||
|
const doorNo = raw.doorNo != null && raw.doorNo != '' ? raw.doorNo : (raw.houseNumber != null ? raw.houseNumber : '')
|
||||||
|
const fullAddressText = raw.fullAddress != null && raw.fullAddress != '' ? raw.fullAddress : locationAddress + ' ' + doorNo
|
||||||
|
return {
|
||||||
|
...raw,
|
||||||
|
addressId: raw.addressId != null && raw.addressId != '' ? raw.addressId : 'local-address-' + Date.now(),
|
||||||
|
userId: raw.userId != null ? raw.userId : '',
|
||||||
|
isDefault: raw.isDefault === true,
|
||||||
|
contactName: raw.contactName != null ? raw.contactName : '',
|
||||||
|
phone: phoneText,
|
||||||
|
contactPhone: phoneText,
|
||||||
|
locationName: locationName,
|
||||||
|
addressName: locationName,
|
||||||
|
locationAddress: locationAddress,
|
||||||
|
addressDetail: locationAddress,
|
||||||
|
doorNo: doorNo,
|
||||||
|
houseNumber: doorNo,
|
||||||
|
remark: raw.remark != null ? raw.remark : '',
|
||||||
|
coordinateType: raw.coordinateType != null && raw.coordinateType != '' ? raw.coordinateType : 'gcj02',
|
||||||
|
latitude: raw.latitude != null ? raw.latitude : 0,
|
||||||
|
longitude: raw.longitude != null ? raw.longitude : 0,
|
||||||
|
createdAt: raw.createdAt != null ? raw.createdAt : Date.now(),
|
||||||
|
updatedAt: raw.updatedAt != null ? raw.updatedAt : Date.now(),
|
||||||
|
fullAddress: fullAddressText.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAddress(address: HomeServiceSelectedAddressType): void {
|
||||||
|
const normalized = normalizeAddress(address)
|
||||||
|
form.addressId = normalized.addressId
|
||||||
|
form.userId = normalized.userId
|
||||||
|
form.isDefault = normalized.isDefault
|
||||||
|
form.contactName = normalized.contactName
|
||||||
|
form.contactPhone = normalized.contactPhone
|
||||||
|
form.phone = normalized.phone != null ? normalized.phone : normalized.contactPhone
|
||||||
|
form.addressName = normalized.addressName
|
||||||
|
form.locationName = normalized.locationName != null ? normalized.locationName : normalized.addressName
|
||||||
|
form.addressDetail = normalized.addressDetail
|
||||||
|
form.locationAddress = normalized.locationAddress != null ? normalized.locationAddress : normalized.addressDetail
|
||||||
|
form.houseNumber = normalized.houseNumber
|
||||||
|
form.doorNo = normalized.doorNo != null ? normalized.doorNo : normalized.houseNumber
|
||||||
|
form.fullAddress = normalized.fullAddress
|
||||||
|
form.latitude = normalized.latitude
|
||||||
|
form.longitude = normalized.longitude
|
||||||
|
form.remark = normalized.remark
|
||||||
|
form.coordinateType = normalized.coordinateType
|
||||||
|
form.createdAt = normalized.createdAt
|
||||||
|
form.updatedAt = normalized.updatedAt
|
||||||
|
editingAddressId = normalized.addressId
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAddressList(): Array<HomeServiceSelectedAddressType> {
|
||||||
|
const stored = uni.getStorageSync(LIST_KEY)
|
||||||
|
if (stored == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof stored === 'string') {
|
||||||
|
const storedText = (stored as string).trim()
|
||||||
|
if (storedText == '') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(storedText) as Array<HomeServiceSelectedAddressType> | null
|
||||||
|
return parsed != null ? parsed : []
|
||||||
|
}
|
||||||
|
return stored as Array<HomeServiceSelectedAddressType>
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析服务地址列表失败', error)
|
||||||
|
uni.removeStorageSync(LIST_KEY)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeAddressList(addresses: Array<HomeServiceSelectedAddressType>): void {
|
||||||
|
uni.setStorageSync(LIST_KEY, JSON.stringify(addresses))
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStoredAddress(rawValue: unknown): HomeServiceSelectedAddressType | null {
|
||||||
|
if (rawValue == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof rawValue === 'string') {
|
||||||
|
const rawText = (rawValue as string).trim()
|
||||||
|
if (rawText == '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(rawText) as HomeServiceSelectedAddressType | null
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
return rawValue as HomeServiceSelectedAddressType
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析服务地址对象失败', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAddressListSeeded(): void {
|
||||||
|
const addresses = readAddressList()
|
||||||
|
if (addresses.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selected = uni.getStorageSync(SELECTED_KEY) as HomeServiceSelectedAddressType | null
|
||||||
|
const normalizedSelected = parseStoredAddress(selected)
|
||||||
|
if (normalizedSelected != null) {
|
||||||
|
const seeded: Array<HomeServiceSelectedAddressType> = []
|
||||||
|
seeded.push(normalizeAddress(normalizedSelected))
|
||||||
|
writeAddressList(seeded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFromAddressId(addressId: string): void {
|
||||||
|
const addresses = readAddressList()
|
||||||
|
for (let i = 0; i < addresses.length; i++) {
|
||||||
|
if (addresses[i].addressId == addressId) {
|
||||||
|
applyAddress(addresses[i])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const selected = parseStoredAddress(uni.getStorageSync(SELECTED_KEY))
|
||||||
|
if (selected != null && selected.addressId == addressId) {
|
||||||
|
applyAddress(selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCachedAddress(): void {
|
||||||
|
if (editingAddressId != '') {
|
||||||
|
loadFromAddressId(editingAddressId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const cachedAddress = parseStoredAddress(uni.getStorageSync(SELECTED_KEY))
|
||||||
|
if (cachedAddress != null) {
|
||||||
|
applyAddress(cachedAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMapDraft(): void {
|
||||||
|
const draft = parseStoredAddress(uni.getStorageSync(MAP_DRAFT_KEY))
|
||||||
|
if (draft == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const normalized = normalizeAddress(draft)
|
||||||
|
form.addressName = normalized.addressName
|
||||||
|
form.locationName = normalized.locationName != null ? normalized.locationName : normalized.addressName
|
||||||
|
form.addressDetail = normalized.addressDetail
|
||||||
|
form.locationAddress = normalized.locationAddress != null ? normalized.locationAddress : normalized.addressDetail
|
||||||
|
form.latitude = normalized.latitude
|
||||||
|
form.longitude = normalized.longitude
|
||||||
|
form.coordinateType = normalized.coordinateType
|
||||||
|
uni.removeStorageSync(MAP_DRAFT_KEY)
|
||||||
|
if (normalized.locationName != '' || normalized.locationAddress != '') {
|
||||||
|
uni.showToast({
|
||||||
|
title: '已回填地图位置',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapChooseLocationResult(name: string, address: string, latitude: number, longitude: number): void {
|
||||||
|
const title = name != '' ? name : address
|
||||||
|
const detail = address != '' ? address : title
|
||||||
|
form.addressName = title
|
||||||
|
form.locationName = title
|
||||||
|
form.addressDetail = detail
|
||||||
|
form.locationAddress = detail
|
||||||
|
form.latitude = latitude
|
||||||
|
form.longitude = longitude
|
||||||
|
form.coordinateType = 'gcj02'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLocationFailToast(error: unknown): string {
|
||||||
|
const errorText = String(error)
|
||||||
|
if (errorText.indexOf('cancel') >= 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (errorText.indexOf('auth deny') >= 0 || errorText.indexOf('authorize') >= 0 || errorText.indexOf('permission') >= 0 || errorText.indexOf('auth denied') >= 0) {
|
||||||
|
return '需要开启定位权限才能选择服务地址'
|
||||||
|
}
|
||||||
|
if (errorText.indexOf('location') >= 0 || errorText.indexOf('getLocation') >= 0) {
|
||||||
|
return '定位失败,请手动搜索或重新选择'
|
||||||
|
}
|
||||||
|
if (errorText.indexOf('map') >= 0 || errorText.indexOf('service') >= 0) {
|
||||||
|
return '地图服务暂不可用,请稍后重试'
|
||||||
|
}
|
||||||
|
return '位置选择失败,请重试'
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseServiceLocation(): void {
|
||||||
|
uni.chooseLocation({
|
||||||
|
success: (res) => {
|
||||||
|
const locationName = res.name != null ? res.name : ''
|
||||||
|
const locationAddress = res.address != null ? res.address : ''
|
||||||
|
mapChooseLocationResult(locationName, locationAddress, res.latitude, res.longitude)
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
const toastText = resolveLocationFailToast(error)
|
||||||
|
if (toastText == '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: toastText,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToMapSelect(): void {
|
||||||
|
uni.navigateTo({ url: '/pages/address/address-map-select' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPhoneValid(phone: string): boolean {
|
||||||
|
return /^\d{11}$/.test(phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAddressPayload(): HomeServiceSelectedAddressType {
|
||||||
|
const now = Date.now()
|
||||||
|
const storedUserId = uni.getStorageSync('user_id') as string | null
|
||||||
|
const phoneText = form.contactPhone != '' ? form.contactPhone : form.phone
|
||||||
|
const locationName = form.locationName != '' ? form.locationName : form.addressName
|
||||||
|
const locationAddress = form.locationAddress != '' ? form.locationAddress : form.addressDetail
|
||||||
|
const doorNo = form.doorNo != '' ? form.doorNo : form.houseNumber
|
||||||
|
return {
|
||||||
|
addressId: form.addressId != '' ? form.addressId : 'local-address-' + now,
|
||||||
|
userId: form.userId != '' ? form.userId : (storedUserId != null ? storedUserId : ''),
|
||||||
|
isDefault: true,
|
||||||
|
contactName: form.contactName,
|
||||||
|
contactPhone: phoneText,
|
||||||
|
phone: phoneText,
|
||||||
|
addressName: locationName,
|
||||||
|
locationName: locationName,
|
||||||
|
addressDetail: locationAddress,
|
||||||
|
locationAddress: locationAddress,
|
||||||
|
houseNumber: doorNo,
|
||||||
|
doorNo: doorNo,
|
||||||
|
fullAddress: (locationAddress + ' ' + doorNo).trim(),
|
||||||
|
latitude: form.latitude,
|
||||||
|
longitude: form.longitude,
|
||||||
|
remark: form.remark,
|
||||||
|
coordinateType: 'gcj02',
|
||||||
|
createdAt: form.createdAt > 0 ? form.createdAt : now,
|
||||||
|
updatedAt: now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAddress(): void {
|
||||||
|
if (form.contactName == '') {
|
||||||
|
uni.showToast({ title: '请输入联系人姓名', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (form.contactPhone == '') {
|
||||||
|
uni.showToast({ title: '请输入联系电话', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isPhoneValid(form.contactPhone)) {
|
||||||
|
uni.showToast({ title: '请输入11位手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!hasLocation()) {
|
||||||
|
uni.showToast({ title: '请选择所在位置', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (form.houseNumber == '') {
|
||||||
|
uni.showToast({ title: '请输入详细门牌号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedAddress = buildAddressPayload()
|
||||||
|
const addresses = readAddressList()
|
||||||
|
let updated = false
|
||||||
|
for (let i = 0; i < addresses.length; i++) {
|
||||||
|
if (addresses[i].addressId == savedAddress.addressId) {
|
||||||
|
addresses[i] = savedAddress
|
||||||
|
updated = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updated) {
|
||||||
|
addresses.unshift(savedAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAddressList(addresses)
|
||||||
|
uni.setStorageSync(SELECTED_KEY, savedAddress)
|
||||||
|
uni.showToast({
|
||||||
|
title: '地址已保存',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
ensureAddressListSeeded()
|
||||||
|
if (options == null) {
|
||||||
|
loadCachedAddress()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const addressId = options['id']
|
||||||
|
if (addressId != null && String(addressId) != '') {
|
||||||
|
editingAddressId = String(addressId)
|
||||||
|
loadFromAddressId(editingAddressId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadCachedAddress()
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
applyMapDraft()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.address-page {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #f4f6f8;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-content {
|
||||||
|
padding: 24rpx 24rpx 180rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-location-card,
|
||||||
|
.form-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title,
|
||||||
|
.form-label,
|
||||||
|
.location-title,
|
||||||
|
.current-location-name,
|
||||||
|
.map-entry-text {
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-location-name,
|
||||||
|
.location-title {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-location-detail,
|
||||||
|
.current-location-placeholder,
|
||||||
|
.location-detail,
|
||||||
|
.location-placeholder,
|
||||||
|
.form-action,
|
||||||
|
.form-input,
|
||||||
|
.form-textarea,
|
||||||
|
.map-entry-arrow {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-location-placeholder,
|
||||||
|
.location-placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-location-detail,
|
||||||
|
.location-detail {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18rpx 0;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-border {
|
||||||
|
border-top: 1rpx solid #eef2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-vertical {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
border-top: 1rpx solid #eef2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-tappable {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 132rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
min-height: 44rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 150rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-action {
|
||||||
|
color: #d97706;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-entry-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 18rpx 0 0;
|
||||||
|
border-top: 1rpx dashed #eef2f7;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-entry-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-entry-arrow {
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 20rpx 24rpx 36rpx;
|
||||||
|
background: rgba(244, 246, 248, 0.96);
|
||||||
|
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: linear-gradient(135deg, #ff8a65 0%, #ff7043 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
390
pages/address/address-list.uvue
Normal file
390
pages/address/address-list.uvue
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
<template>
|
||||||
|
<view class="address-list-page">
|
||||||
|
<scroll-view class="address-scroll" scroll-y="true">
|
||||||
|
<view class="address-content">
|
||||||
|
<view v-if="addresses.length === 0" class="empty-state">
|
||||||
|
<text class="empty-icon">📍</text>
|
||||||
|
<text class="empty-title">还没有服务地址</text>
|
||||||
|
<text class="empty-text">新增一个常用服务地址,预约时会更快</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="item in addresses"
|
||||||
|
:key="item.addressId"
|
||||||
|
:class="['address-card', isSelected(item) ? 'address-card-selected' : '']"
|
||||||
|
@click="selectAddress(item)"
|
||||||
|
>
|
||||||
|
<view class="address-main">
|
||||||
|
<view class="address-top-row">
|
||||||
|
<text class="address-name">{{ item.contactName }}</text>
|
||||||
|
<text class="address-phone">{{ getPhone(item) }}</text>
|
||||||
|
<text v-if="item.isDefault" class="default-tag">默认</text>
|
||||||
|
<text v-if="isSelected(item)" class="selected-tag">当前选择</text>
|
||||||
|
</view>
|
||||||
|
<text class="address-location">{{ getLocationTitle(item) }}</text>
|
||||||
|
<text class="address-full">{{ getFullAddress(item) }}</text>
|
||||||
|
<text v-if="item.remark != ''" class="address-remark">备注:{{ item.remark }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="address-actions">
|
||||||
|
<view class="action-btn" @click.stop="editAddress(item.addressId)">
|
||||||
|
<text class="action-text">编辑</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-btn action-btn-delete" @click.stop="deleteAddress(item.addressId)">
|
||||||
|
<text class="action-text action-text-delete">删除</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="bottom-bar">
|
||||||
|
<button class="add-btn" @click="addAddress">新建服务地址</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
|
import { HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||||||
|
|
||||||
|
const SELECTED_KEY = 'hss_selected_service_address'
|
||||||
|
const LIST_KEY = 'hss_service_address_list'
|
||||||
|
|
||||||
|
const addresses = ref<Array<HomeServiceSelectedAddressType>>([])
|
||||||
|
const selectedAddressId = ref('')
|
||||||
|
|
||||||
|
function normalizeAddress(raw: HomeServiceSelectedAddressType): HomeServiceSelectedAddressType {
|
||||||
|
const phoneText = raw.phone != null && raw.phone != '' ? raw.phone : (raw.contactPhone != null ? raw.contactPhone : '')
|
||||||
|
const locationName = raw.locationName != null && raw.locationName != '' ? raw.locationName : (raw.addressName != null ? raw.addressName : '')
|
||||||
|
const locationAddress = raw.locationAddress != null && raw.locationAddress != '' ? raw.locationAddress : (raw.addressDetail != null ? raw.addressDetail : '')
|
||||||
|
const doorNo = raw.doorNo != null && raw.doorNo != '' ? raw.doorNo : (raw.houseNumber != null ? raw.houseNumber : '')
|
||||||
|
const fullAddressText = raw.fullAddress != null && raw.fullAddress != '' ? raw.fullAddress : locationAddress + ' ' + doorNo
|
||||||
|
return {
|
||||||
|
...raw,
|
||||||
|
addressId: raw.addressId != null && raw.addressId != '' ? raw.addressId : 'local-address-' + Date.now(),
|
||||||
|
userId: raw.userId != null ? raw.userId : '',
|
||||||
|
isDefault: raw.isDefault === true,
|
||||||
|
contactName: raw.contactName != null ? raw.contactName : '',
|
||||||
|
phone: phoneText,
|
||||||
|
contactPhone: phoneText,
|
||||||
|
locationName: locationName,
|
||||||
|
addressName: locationName,
|
||||||
|
locationAddress: locationAddress,
|
||||||
|
addressDetail: locationAddress,
|
||||||
|
doorNo: doorNo,
|
||||||
|
houseNumber: doorNo,
|
||||||
|
remark: raw.remark != null ? raw.remark : '',
|
||||||
|
coordinateType: raw.coordinateType != null && raw.coordinateType != '' ? raw.coordinateType : 'gcj02',
|
||||||
|
latitude: raw.latitude != null ? raw.latitude : 0,
|
||||||
|
longitude: raw.longitude != null ? raw.longitude : 0,
|
||||||
|
createdAt: raw.createdAt != null ? raw.createdAt : Date.now(),
|
||||||
|
updatedAt: raw.updatedAt != null ? raw.updatedAt : Date.now(),
|
||||||
|
fullAddress: fullAddressText.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAddressList(): Array<HomeServiceSelectedAddressType> {
|
||||||
|
const stored = uni.getStorageSync(LIST_KEY)
|
||||||
|
if (stored == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let parsed: Array<HomeServiceSelectedAddressType> = []
|
||||||
|
if (typeof stored === 'string') {
|
||||||
|
const storedText = (stored as string).trim()
|
||||||
|
if (storedText == '') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const parsedValue = JSON.parse(storedText) as Array<HomeServiceSelectedAddressType> | null
|
||||||
|
if (parsedValue == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
parsed = parsedValue
|
||||||
|
} else {
|
||||||
|
parsed = stored as Array<HomeServiceSelectedAddressType>
|
||||||
|
}
|
||||||
|
if (parsed == null || parsed.length == 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const normalized: Array<HomeServiceSelectedAddressType> = []
|
||||||
|
for (let i = 0; i < parsed.length; i++) {
|
||||||
|
if (parsed[i] != null) {
|
||||||
|
normalized.push(normalizeAddress(parsed[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析服务地址列表失败', error)
|
||||||
|
uni.removeStorageSync(LIST_KEY)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeAddressList(list: Array<HomeServiceSelectedAddressType>): void {
|
||||||
|
uni.setStorageSync(LIST_KEY, JSON.stringify(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAddresses(): void {
|
||||||
|
const selected = uni.getStorageSync(SELECTED_KEY) as HomeServiceSelectedAddressType | null
|
||||||
|
if (selected != null) {
|
||||||
|
const normalizedSelected = normalizeAddress(selected)
|
||||||
|
selectedAddressId.value = normalizedSelected.addressId
|
||||||
|
}
|
||||||
|
const list = readAddressList()
|
||||||
|
if (list.length == 0 && selected != null) {
|
||||||
|
const seeded: Array<HomeServiceSelectedAddressType> = []
|
||||||
|
seeded.push(normalizeAddress(selected))
|
||||||
|
addresses.value = seeded
|
||||||
|
writeAddressList(seeded)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addresses.value = list
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(item: HomeServiceSelectedAddressType): boolean {
|
||||||
|
return selectedAddressId.value != '' && item.addressId == selectedAddressId.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPhone(item: HomeServiceSelectedAddressType): string {
|
||||||
|
if (item.phone != null && item.phone != '') {
|
||||||
|
return item.phone
|
||||||
|
}
|
||||||
|
return item.contactPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationTitle(item: HomeServiceSelectedAddressType): string {
|
||||||
|
if (item.locationName != null && item.locationName != '') {
|
||||||
|
return item.locationName
|
||||||
|
}
|
||||||
|
return item.addressName
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFullAddress(item: HomeServiceSelectedAddressType): string {
|
||||||
|
if (item.fullAddress != '') {
|
||||||
|
return item.fullAddress
|
||||||
|
}
|
||||||
|
const locationAddress = item.locationAddress != null && item.locationAddress != '' ? item.locationAddress : item.addressDetail
|
||||||
|
const doorNo = item.doorNo != null && item.doorNo != '' ? item.doorNo : item.houseNumber
|
||||||
|
return (locationAddress + ' ' + doorNo).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAddress(item: HomeServiceSelectedAddressType): void {
|
||||||
|
const normalized = normalizeAddress(item)
|
||||||
|
uni.setStorageSync(SELECTED_KEY, normalized)
|
||||||
|
selectedAddressId.value = normalized.addressId
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAddress(): void {
|
||||||
|
uni.navigateTo({ url: '/pages/address/address-edit' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function editAddress(addressId: string): void {
|
||||||
|
uni.navigateTo({ url: '/pages/address/address-edit?id=' + addressId })
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAddress(addressId: string): void {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除该服务地址吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (!res.confirm) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const nextList: Array<HomeServiceSelectedAddressType> = []
|
||||||
|
for (let i = 0; i < addresses.value.length; i++) {
|
||||||
|
if (addresses.value[i].addressId != addressId) {
|
||||||
|
nextList.push(addresses.value[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addresses.value = nextList
|
||||||
|
writeAddressList(nextList)
|
||||||
|
if (selectedAddressId.value == addressId) {
|
||||||
|
selectedAddressId.value = ''
|
||||||
|
uni.removeStorageSync(SELECTED_KEY)
|
||||||
|
if (nextList.length > 0) {
|
||||||
|
const nextSelected = normalizeAddress(nextList[0])
|
||||||
|
selectedAddressId.value = nextSelected.addressId
|
||||||
|
uni.setStorageSync(SELECTED_KEY, nextSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
loadAddresses()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.address-list-page {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #f4f6f8;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-content {
|
||||||
|
padding: 24rpx 24rpx 180rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card-selected {
|
||||||
|
border-color: rgba(249, 115, 22, 0.38);
|
||||||
|
box-shadow: 0 16rpx 30rpx rgba(249, 115, 22, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-top-row,
|
||||||
|
.address-actions,
|
||||||
|
.bottom-bar,
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-top-row {
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-name,
|
||||||
|
.address-location,
|
||||||
|
.empty-title {
|
||||||
|
color: #1f2937;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-phone,
|
||||||
|
.address-full,
|
||||||
|
.address-remark,
|
||||||
|
.empty-text,
|
||||||
|
.action-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-location {
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-tag,
|
||||||
|
.selected-tag {
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-tag {
|
||||||
|
background: #fff1eb;
|
||||||
|
color: #ff5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-tag {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ea580c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 12rpx 22rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn-delete {
|
||||||
|
background: #fff5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-text-delete {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 120rpx 40rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
|
||||||
|
text-align: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 64rpx;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 20rpx 24rpx 36rpx;
|
||||||
|
background: rgba(244, 246, 248, 0.96);
|
||||||
|
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: linear-gradient(135deg, #ff8a65 0%, #ff7043 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
289
pages/address/address-map-select.uvue
Normal file
289
pages/address/address-map-select.uvue
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<template>
|
||||||
|
<view class="map-page">
|
||||||
|
<view class="search-card">
|
||||||
|
<input v-model="keyword" class="search-input" placeholder="搜索小区、医院、养老院、街道" @input="handleKeywordInput" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<map
|
||||||
|
class="address-map"
|
||||||
|
:latitude="latitude"
|
||||||
|
:longitude="longitude"
|
||||||
|
:markers="markers"
|
||||||
|
:show-location="true"
|
||||||
|
:scale="16"
|
||||||
|
></map>
|
||||||
|
|
||||||
|
<scroll-view class="poi-scroll" scroll-y="true">
|
||||||
|
<view class="poi-card">
|
||||||
|
<text class="poi-title">附近地址</text>
|
||||||
|
<view
|
||||||
|
v-for="item in poiList"
|
||||||
|
:key="item.id"
|
||||||
|
:class="['poi-item', selectedPoiId == item.id ? 'poi-item-selected' : '']"
|
||||||
|
@click="selectPoi(item.id)"
|
||||||
|
>
|
||||||
|
<text class="poi-name">{{ item.name }}</text>
|
||||||
|
<text class="poi-address">{{ item.address }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="bottom-bar">
|
||||||
|
<button class="confirm-btn" @click="confirmAddress">确认地址</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||||||
|
|
||||||
|
const SELECTED_KEY = 'hss_selected_service_address'
|
||||||
|
const MAP_DRAFT_KEY = 'hss_service_address_map_draft'
|
||||||
|
|
||||||
|
type MapPoiItemType = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
address: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = ref('')
|
||||||
|
const latitude = ref(24.28859)
|
||||||
|
const longitude = ref(116.12264)
|
||||||
|
const selectedPoiId = ref('poi-current')
|
||||||
|
const poiList = ref<Array<MapPoiItemType>>([])
|
||||||
|
const markers = ref<Array<UTSJSONObject>>([])
|
||||||
|
|
||||||
|
function buildMarkers(): Array<UTSJSONObject> {
|
||||||
|
const result: Array<UTSJSONObject> = []
|
||||||
|
const selected = getSelectedPoi()
|
||||||
|
if (selected == null) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
const marker = new UTSJSONObject()
|
||||||
|
marker.set('id', 1)
|
||||||
|
marker.set('latitude', selected.latitude)
|
||||||
|
marker.set('longitude', selected.longitude)
|
||||||
|
marker.set('title', selected.name)
|
||||||
|
result.push(marker)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDefaultPoiList(selected: HomeServiceSelectedAddressType | null): Array<MapPoiItemType> {
|
||||||
|
const locationName = selected != null && selected.locationName != null && selected.locationName != '' ? selected.locationName : '当前定位'
|
||||||
|
const locationAddress = selected != null && selected.locationAddress != null && selected.locationAddress != '' ? selected.locationAddress : '请通过系统地图继续完善地址搜索'
|
||||||
|
const baseLatitude = selected != null && selected.latitude != 0 ? selected.latitude : latitude.value
|
||||||
|
const baseLongitude = selected != null && selected.longitude != 0 ? selected.longitude : longitude.value
|
||||||
|
const result: Array<MapPoiItemType> = []
|
||||||
|
result.push({
|
||||||
|
id: 'poi-current',
|
||||||
|
name: locationName,
|
||||||
|
address: locationAddress,
|
||||||
|
latitude: baseLatitude,
|
||||||
|
longitude: baseLongitude
|
||||||
|
})
|
||||||
|
result.push({
|
||||||
|
id: 'poi-near-1',
|
||||||
|
name: locationName + '附近入口',
|
||||||
|
address: locationAddress,
|
||||||
|
latitude: baseLatitude + 0.0006,
|
||||||
|
longitude: baseLongitude + 0.0006
|
||||||
|
})
|
||||||
|
result.push({
|
||||||
|
id: 'poi-near-2',
|
||||||
|
name: locationName + '周边推荐',
|
||||||
|
address: locationAddress,
|
||||||
|
latitude: baseLatitude - 0.0006,
|
||||||
|
longitude: baseLongitude - 0.0006
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedPoi(): MapPoiItemType | null {
|
||||||
|
for (let i = 0; i < poiList.value.length; i++) {
|
||||||
|
if (poiList.value[i].id == selectedPoiId.value) {
|
||||||
|
return poiList.value[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return poiList.value.length > 0 ? poiList.value[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPoi(poiId: string): void {
|
||||||
|
selectedPoiId.value = poiId
|
||||||
|
const selected = getSelectedPoi()
|
||||||
|
if (selected == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
latitude.value = selected.latitude
|
||||||
|
longitude.value = selected.longitude
|
||||||
|
markers.value = buildMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeywordInput(): void {
|
||||||
|
if (keyword.value.trim() == '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const base = getSelectedPoi()
|
||||||
|
if (base == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const dynamicList: Array<MapPoiItemType> = []
|
||||||
|
dynamicList.push({
|
||||||
|
id: 'poi-search-1',
|
||||||
|
name: keyword.value.trim(),
|
||||||
|
address: base.address,
|
||||||
|
latitude: base.latitude,
|
||||||
|
longitude: base.longitude
|
||||||
|
})
|
||||||
|
dynamicList.push(base)
|
||||||
|
poiList.value = dynamicList
|
||||||
|
selectedPoiId.value = 'poi-search-1'
|
||||||
|
markers.value = buildMarkers()
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmAddress(): void {
|
||||||
|
const selected = getSelectedPoi()
|
||||||
|
if (selected == null) {
|
||||||
|
uni.showToast({ title: '请先选择地址', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const draft = {
|
||||||
|
addressId: '',
|
||||||
|
userId: '',
|
||||||
|
isDefault: true,
|
||||||
|
contactName: '',
|
||||||
|
contactPhone: '',
|
||||||
|
phone: '',
|
||||||
|
addressName: selected.name,
|
||||||
|
locationName: selected.name,
|
||||||
|
addressDetail: selected.address,
|
||||||
|
locationAddress: selected.address,
|
||||||
|
houseNumber: '',
|
||||||
|
doorNo: '',
|
||||||
|
fullAddress: selected.address,
|
||||||
|
latitude: selected.latitude,
|
||||||
|
longitude: selected.longitude,
|
||||||
|
remark: '',
|
||||||
|
coordinateType: 'gcj02',
|
||||||
|
createdAt: Date.now(),
|
||||||
|
updatedAt: Date.now()
|
||||||
|
} as HomeServiceSelectedAddressType
|
||||||
|
uni.setStorageSync(MAP_DRAFT_KEY, JSON.stringify(draft))
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
const selected = uni.getStorageSync(SELECTED_KEY) as HomeServiceSelectedAddressType | null
|
||||||
|
if (selected != null && selected.latitude != 0) {
|
||||||
|
latitude.value = selected.latitude
|
||||||
|
longitude.value = selected.longitude
|
||||||
|
}
|
||||||
|
poiList.value = createDefaultPoiList(selected)
|
||||||
|
markers.value = buildMarkers()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.map-page {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #f4f6f8;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24rpx 24rpx 180rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-card,
|
||||||
|
.poi-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
padding: 18rpx 24rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-map {
|
||||||
|
width: 100%;
|
||||||
|
height: 45vh;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-title,
|
||||||
|
.poi-name {
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-item {
|
||||||
|
padding: 18rpx 0;
|
||||||
|
border-top: 1rpx solid #eef2f7;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-item-selected {
|
||||||
|
border-left: 6rpx solid #f97316;
|
||||||
|
padding-left: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poi-address {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 20rpx 24rpx 36rpx;
|
||||||
|
background: rgba(244, 246, 248, 0.96);
|
||||||
|
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: linear-gradient(135deg, #ff8a65 0%, #ff7043 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="cart-search-page">
|
<view class="cart-search-page">
|
||||||
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px', paddingRight: searchHeaderRightPadding + 'px' }">
|
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px', paddingRight: searchHeaderRightPadding + 'px' }">
|
||||||
<view class="back-btn" @click="goBack">
|
<view class="back-btn" @click="goBack">
|
||||||
<text class="back-icon">‹</text>
|
<text class="back-icon">鈥?/text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="search-input-wrap">
|
<view class="search-input-wrap">
|
||||||
<text class="search-icon">⌕</text>
|
<text class="search-icon">鈱?/text>
|
||||||
<input
|
<input
|
||||||
class="search-input"
|
class="search-input"
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
placeholder="搜索购物车商品"
|
placeholder="鎼滅储璐墿杞﹀晢鍝?
|
||||||
confirm-type="search"
|
confirm-type="search"
|
||||||
:focus="true"
|
:focus="true"
|
||||||
@confirm="doSearch"
|
@confirm="doSearch"
|
||||||
/>
|
/>
|
||||||
<view v-if="keyword.length > 0" class="clear-keyword" @click="clearKeyword">
|
<view v-if="keyword.length > 0" class="clear-keyword" @click="clearKeyword">
|
||||||
<text class="clear-keyword-text">×</text>
|
<text class="clear-keyword-text">脳</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="search-btn" @click="doSearch">
|
<view class="search-btn" @click="doSearch">
|
||||||
<text class="search-btn-text">搜索</text>
|
<text class="search-btn-text">鎼滅储</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view v-if="!hasSearched" class="search-content" :scroll-y="true" :show-scrollbar="false">
|
<scroll-view v-if="!hasSearched" class="search-content" :scroll-y="true" :show-scrollbar="false">
|
||||||
<view class="history-section">
|
<view class="history-section">
|
||||||
<view class="section-header">
|
<view class="section-header">
|
||||||
<text class="section-title">历史搜索</text>
|
<text class="section-title">鍘嗗彶鎼滅储</text>
|
||||||
<text class="clear-history" @click="clearSearchHistory">清空</text>
|
<text class="clear-history" @click="clearSearchHistory">娓呯┖</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="searchHistory.length > 0" class="history-list">
|
<view v-if="searchHistory.length > 0" class="history-list">
|
||||||
@@ -44,13 +44,13 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="empty-history">
|
<view v-else class="empty-history">
|
||||||
<text class="empty-history-text">暂无历史搜索</text>
|
<text class="empty-history-text">鏆傛棤鍘嗗彶鎼滅储</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="discover-section">
|
<view class="discover-section">
|
||||||
<view class="section-header">
|
<view class="section-header">
|
||||||
<text class="section-title">搜索发现</text>
|
<text class="section-title">鎼滅储鍙戠幇</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="discover-grid">
|
<view class="discover-grid">
|
||||||
@@ -66,10 +66,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<scroll-view v-else class="search-result-content" :scroll-y="true" :show-scrollbar="false">
|
<scroll-view v-else class="search-result-content" :scroll-y="true" :show-scrollbar="false" :lower-threshold="120" @scrolltolower="handleResultScrollToLower">
|
||||||
<view v-if="matchedCartItems.length > 0" class="cart-match-section">
|
<view v-if="matchedCartItems.length > 0" class="cart-match-section">
|
||||||
<view class="result-section-title-wrap">
|
<view class="result-section-title-wrap">
|
||||||
<text class="result-section-title">购物车内相关商品</text>
|
<text class="result-section-title">璐墿杞﹀唴鐩稿叧鍟嗗搧</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="cart-result-list">
|
<view class="cart-result-list">
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
class="cart-result-card"
|
class="cart-result-card"
|
||||||
>
|
>
|
||||||
<view class="item-select" @click="toggleSelect(item.id)">
|
<view class="item-select" @click="toggleSelect(item.id)">
|
||||||
<text v-if="item.selected" class="selected-icon">✓</text>
|
<text v-if="item.selected" class="selected-icon">鉁?/text>
|
||||||
<text v-else class="unselected-icon"></text>
|
<text v-else class="unselected-icon"></text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
<text class="item-name" :lines="1">{{ item.name }}</text>
|
<text class="item-name" :lines="1">{{ item.name }}</text>
|
||||||
<text class="item-spec">{{ item.spec }}</text>
|
<text class="item-spec">{{ item.spec }}</text>
|
||||||
<view class="item-footer">
|
<view class="item-footer">
|
||||||
<text class="item-price">¥{{ item.price }}</text>
|
<text class="item-price">楼{{ item.price }}</text>
|
||||||
<view class="quantity-control">
|
<view class="quantity-control">
|
||||||
<text class="quantity-btn" @click="decreaseQuantity(item.id)">-</text>
|
<text class="quantity-btn" @click="decreaseQuantity(item.id)">-</text>
|
||||||
<text class="quantity-value">{{ item.quantity }}</text>
|
<text class="quantity-value">{{ item.quantity }}</text>
|
||||||
@@ -112,31 +112,17 @@
|
|||||||
<view class="recommend-search-section">
|
<view class="recommend-search-section">
|
||||||
<view class="recommend-title-wrap">
|
<view class="recommend-title-wrap">
|
||||||
<view class="line"></view>
|
<view class="line"></view>
|
||||||
<text class="recommend-title">{{ matchedCartItems.length > 0 ? '为你搜索全站商品' : '为你搜索全部商品' }}</text>
|
<text class="recommend-title">{{ matchedCartItems.length > 0 ? '涓轰綘鎼滅储鍏ㄧ珯鍟嗗搧' : '涓轰綘鎼滅储鍏ㄩ儴鍟嗗搧' }}</text>
|
||||||
<view class="line"></view>
|
<view class="line"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="recommend-grid">
|
<GuessYouLike
|
||||||
<view
|
title="猜你喜欢"
|
||||||
v-for="product in recommendProducts"
|
:pageSize="8"
|
||||||
:key="product.id"
|
:excludeProductIds="matchedProductIds"
|
||||||
class="recommend-card"
|
:loadMoreKey="guessLoadMoreKey"
|
||||||
@click="goToProductDetail(product)"
|
@productClick="goToRecommendProductDetail"
|
||||||
>
|
/>
|
||||||
<image class="recommend-image" :src="product.image" mode="aspectFill" />
|
|
||||||
<view class="recommend-info">
|
|
||||||
<text class="recommend-shop-tag">{{ product.shopName }}</text>
|
|
||||||
<text class="recommend-name" :lines="2">{{ product.name }}</text>
|
|
||||||
<text class="recommend-sales">{{ product.salesText }}</text>
|
|
||||||
<view class="recommend-price-row">
|
|
||||||
<text class="recommend-price">¥{{ product.price }}</text>
|
|
||||||
<view class="recommend-cart-btn" @click.stop="addRecommendToCart(product)">
|
|
||||||
<text class="recommend-cart-icon">🛒</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="bottom-safe-space"></view>
|
<view class="bottom-safe-space"></view>
|
||||||
</view>
|
</view>
|
||||||
@@ -146,19 +132,19 @@
|
|||||||
<view class="settlement-inner">
|
<view class="settlement-inner">
|
||||||
<view class="settlement-left" @click="toggleSelectAllInSearch">
|
<view class="settlement-left" @click="toggleSelectAllInSearch">
|
||||||
<view class="select-circle" :class="{ 'select-circle-active': allSearchSelected }">
|
<view class="select-circle" :class="{ 'select-circle-active': allSearchSelected }">
|
||||||
<text v-if="allSearchSelected" class="select-check">✓</text>
|
<text v-if="allSearchSelected" class="select-check">鉁?/text>
|
||||||
</view>
|
</view>
|
||||||
<text class="select-all-text">全选</text>
|
<text class="select-all-text">鍏ㄩ€?/text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="settlement-right">
|
<view class="settlement-right">
|
||||||
<view class="total-info">
|
<view class="total-info">
|
||||||
<text class="total-label">合计:</text>
|
<text class="total-label">鍚堣:</text>
|
||||||
<text class="total-price">¥{{ searchTotalPrice }}</text>
|
<text class="total-price">楼{{ searchTotalPrice }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="checkout-btn" :class="{ 'checkout-btn-disabled': searchSelectedCount == 0 }" @click="goToCheckoutFromSearch">
|
<button class="checkout-btn" :class="{ 'checkout-btn-disabled': searchSelectedCount == 0 }" @click="goToCheckoutFromSearch">
|
||||||
去结算({{ searchSelectedCount }})
|
鍘荤粨绠?{{ searchSelectedCount }})
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -171,6 +157,7 @@ import { computed, ref } from 'vue'
|
|||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'
|
import { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'
|
||||||
import { goToLogin } from '@/utils/utils.uts'
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||||||
|
|
||||||
const CART_SEARCH_HISTORY_KEY = 'cart_search_history'
|
const CART_SEARCH_HISTORY_KEY = 'cart_search_history'
|
||||||
|
|
||||||
@@ -211,19 +198,19 @@ const keyword = ref<string>('')
|
|||||||
const hasSearched = ref<boolean>(false)
|
const hasSearched = ref<boolean>(false)
|
||||||
const searchHistory = ref<Array<string>>([])
|
const searchHistory = ref<Array<string>>([])
|
||||||
const searchDiscoverList = ref<Array<string>>([
|
const searchDiscoverList = ref<Array<string>>([
|
||||||
'无人机',
|
'鏃犱汉鏈?,
|
||||||
'水杯',
|
'姘存澂',
|
||||||
'手机',
|
'鎵嬫満',
|
||||||
'天然泉水',
|
'澶╃劧娉夋按',
|
||||||
'按摩仪',
|
'鎸夋懇浠?,
|
||||||
'摄像头',
|
'鎽勫儚澶?,
|
||||||
'耳机',
|
'鑰虫満',
|
||||||
'停车场设备',
|
'鍋滆溅鍦鸿澶?,
|
||||||
'饮料',
|
'楗枡',
|
||||||
'iPhone'
|
'iPhone'
|
||||||
])
|
])
|
||||||
const cartItems = ref<Array<LocalCartItem>>([])
|
const cartItems = ref<Array<LocalCartItem>>([])
|
||||||
const recommendProducts = ref<Array<ProductItem>>([])
|
const guessLoadMoreKey = ref<number>(0)
|
||||||
const isLoading = ref<boolean>(false)
|
const isLoading = ref<boolean>(false)
|
||||||
const statusBarHeight = ref<number>(0)
|
const statusBarHeight = ref<number>(0)
|
||||||
const updatingItems = ref<Set<string>>(new Set())
|
const updatingItems = ref<Set<string>>(new Set())
|
||||||
@@ -233,7 +220,7 @@ const searchDiscoverWords = computed<Array<string>>(() => {
|
|||||||
if (searchDiscoverList.value.length > 0) {
|
if (searchDiscoverList.value.length > 0) {
|
||||||
return searchDiscoverList.value
|
return searchDiscoverList.value
|
||||||
}
|
}
|
||||||
return ['无人机', '水杯', '手机', '天然泉水']
|
return ['鏃犱汉鏈?, '姘存澂', '鎵嬫満', '澶╃劧娉夋按']
|
||||||
})
|
})
|
||||||
|
|
||||||
const safeLower = (value: string): string => {
|
const safeLower = (value: string): string => {
|
||||||
@@ -246,8 +233,8 @@ const matchedCartItems = computed<Array<LocalCartItem>>(() => {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return cartItems.value.filter((item: LocalCartItem) => {
|
return cartItems.value.filter((item: LocalCartItem) => {
|
||||||
const title = safeLower(item.name)
|
const title = safeLower(item.name)
|
||||||
const name = safeLower(item.name)
|
const name = safeLower(item.name)
|
||||||
const productName = safeLower(item.productName)
|
const productName = safeLower(item.productName)
|
||||||
const skuName = safeLower(item.skuName)
|
const skuName = safeLower(item.skuName)
|
||||||
@@ -256,17 +243,28 @@ const matchedCartItems = computed<Array<LocalCartItem>>(() => {
|
|||||||
const merchantName = safeLower(item.merchantName)
|
const merchantName = safeLower(item.merchantName)
|
||||||
const brandName = safeLower(item.brandName)
|
const brandName = safeLower(item.brandName)
|
||||||
|
|
||||||
return title.indexOf(q) >= 0
|
return title.indexOf(q) >= 0
|
||||||
|| name.indexOf(q) >= 0
|
|| name.indexOf(q) >= 0
|
||||||
|| productName.indexOf(q) >= 0
|
|| productName.indexOf(q) >= 0
|
||||||
|| skuName.indexOf(q) >= 0
|
|| skuName.indexOf(q) >= 0
|
||||||
|| specName.indexOf(q) >= 0
|
|| specName.indexOf(q) >= 0
|
||||||
|| shopName.indexOf(q) >= 0
|
|| shopName.indexOf(q) >= 0
|
||||||
|| merchantName.indexOf(q) >= 0
|
|| merchantName.indexOf(q) >= 0
|
||||||
|| brandName.indexOf(q) >= 0
|
|| brandName.indexOf(q) >= 0
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
|
const matchedProductIds = computed<Array<string>>(() => {
|
||||||
|
const ids: Array<string> = []
|
||||||
|
for (let i = 0; i < matchedCartItems.value.length; i++) {
|
||||||
|
const item = matchedCartItems.value[i]
|
||||||
|
const productId = item.productId !== '' ? item.productId : item.id
|
||||||
|
if (productId !== '' && ids.indexOf(productId) < 0) {
|
||||||
|
ids.push(productId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
})
|
||||||
const searchSelectedItems = computed<Array<LocalCartItem>>(() => {
|
const searchSelectedItems = computed<Array<LocalCartItem>>(() => {
|
||||||
return matchedCartItems.value.filter((item: LocalCartItem) => item.selected == true)
|
return matchedCartItems.value.filter((item: LocalCartItem) => item.selected == true)
|
||||||
})
|
})
|
||||||
@@ -291,9 +289,9 @@ const allSearchSelected = computed((): boolean => {
|
|||||||
|
|
||||||
const noCartResultText = computed((): string => {
|
const noCartResultText = computed((): string => {
|
||||||
if (cartItems.value.length == 0) {
|
if (cartItems.value.length == 0) {
|
||||||
return '购物车为空,暂无相关商品'
|
return '璐墿杞︿负绌猴紝鏆傛棤鐩稿叧鍟嗗搧'
|
||||||
}
|
}
|
||||||
return '您的购物车里没有相关商品'
|
return '鎮ㄧ殑璐墿杞﹂噷娌℃湁鐩稿叧鍟嗗搧'
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchHeaderRightPadding = computed((): number => {
|
const searchHeaderRightPadding = computed((): number => {
|
||||||
@@ -326,7 +324,7 @@ const loadSearchHistory = () => {
|
|||||||
const parsed = JSON.parse(cache) as Array<string>
|
const parsed = JSON.parse(cache) as Array<string>
|
||||||
searchHistory.value = parsed
|
searchHistory.value = parsed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析搜索历史失败:', e)
|
console.error('瑙f瀽鎼滅储鍘嗗彶澶辫触:', e)
|
||||||
searchHistory.value = []
|
searchHistory.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,81 +350,6 @@ const clearSearchHistory = () => {
|
|||||||
uni.removeStorageSync(CART_SEARCH_HISTORY_KEY)
|
uni.removeStorageSync(CART_SEARCH_HISTORY_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockRecommendProducts = (q: string): Array<ProductItem> => {
|
|
||||||
const text = q.trim() == '' ? '热卖好物' : q
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'mock-1',
|
|
||||||
name: text + ' 便携款',
|
|
||||||
price: 99,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
shopName: '平台精选',
|
|
||||||
salesText: '已售 200+',
|
|
||||||
merchantId: '',
|
|
||||||
skuId: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mock-2',
|
|
||||||
name: text + ' 升级版',
|
|
||||||
price: 159,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
shopName: '品牌旗舰',
|
|
||||||
salesText: '好评 98%',
|
|
||||||
merchantId: '',
|
|
||||||
skuId: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mock-3',
|
|
||||||
name: text + ' 热销套装',
|
|
||||||
price: 239,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
shopName: '今日推荐',
|
|
||||||
salesText: '月销 500+',
|
|
||||||
merchantId: '',
|
|
||||||
skuId: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mock-4',
|
|
||||||
name: text + ' 家用精选',
|
|
||||||
price: 79,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
shopName: '官方自营',
|
|
||||||
salesText: '已售 1200+',
|
|
||||||
merchantId: '',
|
|
||||||
skuId: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadRecommendProducts = async (q: string) => {
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const result = await supabaseService.searchProducts(q, 1, 8, 'sales')
|
|
||||||
if (result.data.length > 0) {
|
|
||||||
recommendProducts.value = result.data.map((product: Product): ProductItem => {
|
|
||||||
const saleCount = product.sale_count ?? product.sales ?? 0
|
|
||||||
return {
|
|
||||||
id: product.id,
|
|
||||||
name: product.name,
|
|
||||||
price: product.base_price ?? product.market_price ?? 0,
|
|
||||||
image: product.main_image_url ?? product.image_url ?? '/static/images/default.png',
|
|
||||||
shopName: product.shop_name ?? '平台精选',
|
|
||||||
salesText: '已售 ' + saleCount + '+',
|
|
||||||
merchantId: product.merchant_id ?? '',
|
|
||||||
skuId: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
recommendProducts.value = mockRecommendProducts(q)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('加载推荐商品失败:', e)
|
|
||||||
recommendProducts.value = mockRecommendProducts(q)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadCartData = async () => {
|
const loadCartData = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -438,7 +361,7 @@ const loadCartData = async () => {
|
|||||||
memberDiscount = discountRaw as number
|
memberDiscount = discountRaw as number
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('获取会员信息失败,使用默认折扣:', e)
|
console.log('鑾峰彇浼氬憳淇℃伅澶辫触锛屼娇鐢ㄩ粯璁ゆ姌鎵?', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const supabaseCartItems = await supabaseService.getCartItems()
|
const supabaseCartItems = await supabaseService.getCartItems()
|
||||||
@@ -449,18 +372,18 @@ const loadCartData = async () => {
|
|||||||
memberPrice = Math.round(originalPrice * memberDiscount * 100) / 100
|
memberPrice = Math.round(originalPrice * memberDiscount * 100) / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
const productName = item.product_name ?? '未知商品'
|
const productName = item.product_name ?? '鏈煡鍟嗗搧'
|
||||||
const specName = item.product_specification ?? '标准规格'
|
const specName = item.product_specification ?? '鏍囧噯瑙勬牸'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
shopId: item.shop_id ?? 'default_shop',
|
shopId: item.shop_id ?? 'default_shop',
|
||||||
shopName: item.shop_name ?? '商城优选',
|
shopName: item.shop_name ?? '鍟嗗煄浼橀€?,
|
||||||
name: productName,
|
name: productName,
|
||||||
productName: productName,
|
productName: productName,
|
||||||
skuName: specName,
|
skuName: specName,
|
||||||
specName: specName,
|
specName: specName,
|
||||||
merchantName: item.shop_name ?? '商城优选',
|
merchantName: item.shop_name ?? '鍟嗗煄浼橀€?,
|
||||||
brandName: '',
|
brandName: '',
|
||||||
price: originalPrice,
|
price: originalPrice,
|
||||||
originalPrice: originalPrice,
|
originalPrice: originalPrice,
|
||||||
@@ -475,7 +398,7 @@ const loadCartData = async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载购物车搜索数据失败:', e)
|
console.error('鍔犺浇璐墿杞︽悳绱㈡暟鎹け璐?', e)
|
||||||
cartItems.value = []
|
cartItems.value = []
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -486,7 +409,7 @@ const doSearch = async () => {
|
|||||||
const q = keyword.value.trim()
|
const q = keyword.value.trim()
|
||||||
if (q == '') {
|
if (q == '') {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请输入搜索关键词',
|
title: '璇疯緭鍏ユ悳绱㈠叧閿瘝',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -494,7 +417,6 @@ const doSearch = async () => {
|
|||||||
|
|
||||||
hasSearched.value = true
|
hasSearched.value = true
|
||||||
saveSearchHistory(q)
|
saveSearchHistory(q)
|
||||||
await loadRecommendProducts(q)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useSearchWord = (word: string) => {
|
const useSearchWord = (word: string) => {
|
||||||
@@ -509,7 +431,7 @@ const goBack = () => {
|
|||||||
const clearKeyword = () => {
|
const clearKeyword = () => {
|
||||||
keyword.value = ''
|
keyword.value = ''
|
||||||
hasSearched.value = false
|
hasSearched.value = false
|
||||||
recommendProducts.value = []
|
guessLoadMoreKey.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleSelect = async (itemId: string) => {
|
const toggleSelect = async (itemId: string) => {
|
||||||
@@ -524,7 +446,7 @@ const toggleSelect = async (itemId: string) => {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
cartItems.value[index].selected = !newSelected
|
cartItems.value[index].selected = !newSelected
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
uni.showToast({ title: '缃戠粶寮傚父锛岃閲嶈瘯', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +465,7 @@ const increaseQuantity = async (itemId: string) => {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
cartItems.value[index].quantity = newQuantity - 1
|
cartItems.value[index].quantity = newQuantity - 1
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
uni.showToast({ title: '更新失败', icon: 'none' })
|
uni.showToast({ title: '鏇存柊澶辫触', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,7 +475,7 @@ const decreaseQuantity = async (itemId: string) => {
|
|||||||
if (index == -1) return
|
if (index == -1) return
|
||||||
|
|
||||||
if (cartItems.value[index].quantity <= 1) {
|
if (cartItems.value[index].quantity <= 1) {
|
||||||
uni.showToast({ title: '最少保留1件,可返回购物车删除', icon: 'none' })
|
uni.showToast({ title: '鏈€灏戜繚鐣?浠讹紝鍙繑鍥炶喘鐗╄溅鍒犻櫎', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,7 +489,7 @@ const decreaseQuantity = async (itemId: string) => {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
cartItems.value[index].quantity = newQuantity + 1
|
cartItems.value[index].quantity = newQuantity + 1
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
uni.showToast({ title: '更新失败', icon: 'none' })
|
uni.showToast({ title: '鏇存柊澶辫触', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,14 +509,14 @@ const toggleSelectAllInSearch = async () => {
|
|||||||
item.selected = !checked
|
item.selected = !checked
|
||||||
})
|
})
|
||||||
cartItems.value = [...cartItems.value]
|
cartItems.value = [...cartItems.value]
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
uni.showToast({ title: '鎿嶄綔澶辫触', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToCheckoutFromSearch = () => {
|
const goToCheckoutFromSearch = () => {
|
||||||
if (searchSelectedCount.value == 0) {
|
if (searchSelectedCount.value == 0) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请选择商品',
|
title: '璇烽€夋嫨鍟嗗搧',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -620,8 +542,8 @@ const goToCheckoutFromSearch = () => {
|
|||||||
try {
|
try {
|
||||||
uni.setStorageSync('checkout_items', JSON.stringify(selectedItems))
|
uni.setStorageSync('checkout_items', JSON.stringify(selectedItems))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('存储结算数据失败', e)
|
console.error('瀛樺偍缁撶畻鏁版嵁澶辫触', e)
|
||||||
uni.showToast({ title: '系统异常,请重试', icon: 'none' })
|
uni.showToast({ title: '绯荤粺寮傚父锛岃閲嶈瘯', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,38 +575,17 @@ const goToProductDetail = (product: ProductItem) => {
|
|||||||
uni.navigateTo({ url })
|
uni.navigateTo({ url })
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRecommendToCart = async (product: ProductItem) => {
|
const goToRecommendProductDetail = (productId: string) => {
|
||||||
const userId = supabaseService.getCurrentUserId()
|
if (productId === '') {
|
||||||
if (userId == null || userId == '') {
|
|
||||||
goToLogin('/pages/main/cart-search/cart-search')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/mall/consumer/product-detail?id=' + encodeURIComponent(productId) + '&productId=' + encodeURIComponent(productId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
uni.showLoading({ title: '添加中...' })
|
const handleResultScrollToLower = () => {
|
||||||
try {
|
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||||
const skus = await supabaseService.getProductSkus(product.id)
|
|
||||||
if (skus.length > 0) {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({ title: '请选择规格', icon: 'none' })
|
|
||||||
setTimeout(() => {
|
|
||||||
goToProductDetail(product)
|
|
||||||
}, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = await supabaseService.addToCart(product.id, 1, product.skuId, product.merchantId)
|
|
||||||
uni.hideLoading()
|
|
||||||
if (success) {
|
|
||||||
uni.showToast({ title: '已添加到购物车', icon: 'success' })
|
|
||||||
loadCartData()
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '添加失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('推荐商品加入购物车失败:', e)
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({ title: '添加失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad((options: UTSJSONObject) => {
|
onLoad((options: UTSJSONObject) => {
|
||||||
@@ -698,7 +599,7 @@ onLoad((options: UTSJSONObject) => {
|
|||||||
navBarRight.value = (systemInfo.screenWidth - menuButton.left) + 10
|
navBarRight.value = (systemInfo.screenWidth - menuButton.left) + 10
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('获取胶囊按钮信息失败:', e)
|
console.log('鑾峰彇鑳跺泭鎸夐挳淇℃伅澶辫触:', e)
|
||||||
navBarRight.value = 96
|
navBarRight.value = 96
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
@@ -1304,4 +1205,4 @@ onLoad((options: UTSJSONObject) => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -174,40 +174,13 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 推荐商品 -->
|
<GuessYouLike
|
||||||
<view v-if="currentCartType == 'goods' && recommendProducts.length > 0" class="recommend-section">
|
v-if="currentCartType == 'goods'"
|
||||||
<view class="section-header">
|
title="猜你喜欢"
|
||||||
<text class="section-title">猜你喜欢</text>
|
:pageSize="8"
|
||||||
</view>
|
:loadMoreKey="guessLoadMoreKey"
|
||||||
<view class="recommend-list">
|
@productClick="handleGuessProductClick"
|
||||||
<view
|
/>
|
||||||
v-for="product in recommendProducts"
|
|
||||||
:key="product.id"
|
|
||||||
class="recommend-item"
|
|
||||||
@click="navigateToProduct(product)"
|
|
||||||
>
|
|
||||||
<view class="recommend-image-wrapper">
|
|
||||||
<image
|
|
||||||
class="recommend-image"
|
|
||||||
:src="product.image"
|
|
||||||
mode="aspectFill"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<text class="recommend-name" :lines="2">{{ product.name }}</text>
|
|
||||||
<view class="recommend-bottom">
|
|
||||||
<text class="recommend-price">¥{{ product.price }}</text>
|
|
||||||
<view class="recommend-add-btn" @click.stop="addToCart(product)">
|
|
||||||
<text class="recommend-add-icon">+</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="recommend-load-more" @click="loadRecommendProducts(false)">
|
|
||||||
<text v-if="recommendLoading" class="recommend-load-text">正在加载更多...</text>
|
|
||||||
<text v-else-if="!recommendHasMore && recommendInitialized" class="recommend-load-text">没有更多了</text>
|
|
||||||
<text v-else class="recommend-load-text">上拉加载更多</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 底部占位符:确保内容不被原生 TabBar 遮挡 -->
|
<!-- 底部占位符:确保内容不被原生 TabBar 遮挡 -->
|
||||||
<view class="tabbar-safe-area"></view>
|
<view class="tabbar-safe-area"></view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -280,6 +253,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { onShow } from '@dcloudio/uni-app'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'
|
import { supabaseService, type CartItem as SupabaseCartItem, type Product } from '@/utils/supabaseService.uts'
|
||||||
import { goToLogin } from '@/utils/utils.uts'
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||||||
|
|
||||||
type ModalSuccess = { confirm: boolean; cancel: boolean }
|
type ModalSuccess = { confirm: boolean; cancel: boolean }
|
||||||
|
|
||||||
@@ -375,6 +349,7 @@ const recommendBottomLocked = ref<boolean>(false)
|
|||||||
const recommendViewportHeight = ref<number>(0)
|
const recommendViewportHeight = ref<number>(0)
|
||||||
const pageWindowHeight = ref<number>(0)
|
const pageWindowHeight = ref<number>(0)
|
||||||
const isAndroidApp = ref<boolean>(false)
|
const isAndroidApp = ref<boolean>(false)
|
||||||
|
const guessLoadMoreKey = ref<number>(0)
|
||||||
const serviceMockItems = ref<ServiceCartItem[]>([
|
const serviceMockItems = ref<ServiceCartItem[]>([
|
||||||
{
|
{
|
||||||
id: 'service-1',
|
id: 'service-1',
|
||||||
@@ -725,27 +700,6 @@ const mergeRecommendProducts = (oldList: RecommendProduct[], newList: RecommendP
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockRecommendProducts = (page: number, pageSize: number): RecommendProduct[] => {
|
|
||||||
const list: RecommendProduct[] = []
|
|
||||||
if (page >= 5) {
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
for (let i = 0; i < pageSize; i++) {
|
|
||||||
const index = (page - 1) * pageSize + i + 1
|
|
||||||
list.push({
|
|
||||||
id: 'mock-recommend-' + index,
|
|
||||||
shopId: 'mock-shop-' + index,
|
|
||||||
shopName: '商城推荐',
|
|
||||||
name: '猜你喜欢 推荐商品 ' + index,
|
|
||||||
price: 9.9 + index,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
skuId: '',
|
|
||||||
merchant_id: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchRecommendProducts = async (page: number, pageSize: number): Promise<RecommendProduct[]> => {
|
const fetchRecommendProducts = async (page: number, pageSize: number): Promise<RecommendProduct[]> => {
|
||||||
console.log('[cart推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
console.log('[cart推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
||||||
const hotResp = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
const hotResp = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
||||||
@@ -851,13 +805,7 @@ async function loadRecommendProducts(reset: boolean): Promise<void> {
|
|||||||
recommendInitialized.value = true
|
recommendInitialized.value = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载推荐商品失败:', error)
|
console.error('加载推荐商品失败:', error)
|
||||||
if (reset && recommendProducts.value.length === 0) {
|
recommendHasMore.value = false
|
||||||
const mockList = mockRecommendProducts(1, recommendPageSize.value)
|
|
||||||
recommendProducts.value = mockList
|
|
||||||
recommendPage.value = 2
|
|
||||||
recommendHasMore.value = true
|
|
||||||
recommendInitialized.value = true
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
recommendLoading.value = false
|
recommendLoading.value = false
|
||||||
if (!reset && recommendPendingLoad.value && recommendHasMore.value) {
|
if (!reset && recommendPendingLoad.value && recommendHasMore.value) {
|
||||||
@@ -869,59 +817,13 @@ async function loadRecommendProducts(reset: boolean): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onRecommendScrollToLower(): void {
|
function onRecommendScrollToLower(): void {
|
||||||
console.log('[cart推荐] scrolltolower 触发 currentCartType=', currentCartType.value, 'initialized=', recommendInitialized.value)
|
if (currentCartType.value == 'goods') {
|
||||||
if (currentCartType.value != 'goods') {
|
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||||
console.log('[cart推荐] 跳过:当前不是 goods')
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
recommendBottomLocked.value = true
|
|
||||||
loadRecommendProducts(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRecommendScroll(event: any): void {
|
function onRecommendScroll(event: any): void {
|
||||||
if (currentCartType.value != 'goods' || recommendLoading.value || !recommendHasMore.value) {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const eventObj = toRecommendScrollJson(event)
|
|
||||||
let detailObj: UTSJSONObject | null = null
|
|
||||||
if (eventObj != null) {
|
|
||||||
detailObj = toRecommendScrollJson(eventObj.get('detail'))
|
|
||||||
}
|
|
||||||
if (detailObj == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollTop = readRecommendScrollMetric(detailObj, 'scrollTop')
|
|
||||||
const scrollHeight = readRecommendScrollMetric(detailObj, 'scrollHeight')
|
|
||||||
let clientHeight = readRecommendScrollMetric(detailObj, 'clientHeight')
|
|
||||||
|
|
||||||
if (clientHeight <= 0) {
|
|
||||||
clientHeight = recommendViewportHeight.value
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[cart推荐] scroll事件 scrollTop=', scrollTop, 'scrollHeight=', scrollHeight, 'clientHeight=', clientHeight)
|
|
||||||
|
|
||||||
if (scrollHeight <= 0 || clientHeight <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const distanceToBottom = scrollHeight - scrollTop - clientHeight
|
|
||||||
if (distanceToBottom > 260) {
|
|
||||||
recommendBottomLocked.value = false
|
|
||||||
}
|
|
||||||
if (distanceToBottom <= 180) {
|
|
||||||
console.log('[cart推荐] scroll 兜底触底 distanceToBottom=', distanceToBottom)
|
|
||||||
if (recommendBottomLocked.value) {
|
|
||||||
recommendPendingLoad.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recommendBottomLocked.value = true
|
|
||||||
loadRecommendProducts(false)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[cart推荐] 处理推荐滚动失败:', e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
@@ -981,9 +883,6 @@ const loadCartData = async () => {
|
|||||||
console.log('Transformed items count:', transformedItems.length);
|
console.log('Transformed items count:', transformedItems.length);
|
||||||
cartItems.value = transformedItems
|
cartItems.value = transformedItems
|
||||||
|
|
||||||
if (!recommendInitialized.value) {
|
|
||||||
await loadRecommendProducts(true)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载购物车数据失败:', error)
|
console.error('加载购物车数据失败:', error)
|
||||||
cartItems.value = []
|
cartItems.value = []
|
||||||
@@ -996,6 +895,10 @@ onShow(() => {
|
|||||||
loadCartData()
|
loadCartData()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleGuessProductClick = (productId: string) => {
|
||||||
|
navigateToProduct({ id: productId, productId: productId, price: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
// 商品操作 - 更新选中状态到Supabase
|
// 商品操作 - 更新选中状态到Supabase
|
||||||
const toggleSelect = async (itemId: string) => {
|
const toggleSelect = async (itemId: string) => {
|
||||||
// 乐观更新
|
// 乐观更新
|
||||||
@@ -2593,4 +2496,3 @@ const goToCheckout = () => {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -436,68 +436,6 @@ function buildServiceImageText(categoryId: string): string {
|
|||||||
return '服'
|
return '服'
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMockServiceProducts(): Array<HomeCareServiceProductType> {
|
|
||||||
// TODO: 后续替换为服务首页专用接口,当前仅在真实服务目录为空时兜底。
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'svc-001',
|
|
||||||
title: '基础上门照护',
|
|
||||||
subtitle: '协助起居、日常陪护、健康观察',
|
|
||||||
categoryId: 'basic_care',
|
|
||||||
price: 99,
|
|
||||||
unit: '次',
|
|
||||||
tags: ['平台认证', '可预约'],
|
|
||||||
salesText: '已服务230+',
|
|
||||||
imageText: '护',
|
|
||||||
coverGradient: getServiceGradient('basic_care'),
|
|
||||||
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-001',
|
|
||||||
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-001&mode=booking'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svc-002',
|
|
||||||
title: '居家康复指导',
|
|
||||||
subtitle: '术后恢复、动作训练、康复评估',
|
|
||||||
categoryId: 'rehab',
|
|
||||||
price: 129,
|
|
||||||
unit: '次',
|
|
||||||
tags: ['康复指导', '上门服务'],
|
|
||||||
salesText: '已服务180+',
|
|
||||||
imageText: '康',
|
|
||||||
coverGradient: getServiceGradient('rehab'),
|
|
||||||
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-002',
|
|
||||||
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-002&mode=booking'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svc-mock-escort',
|
|
||||||
title: '陪诊陪护服务',
|
|
||||||
subtitle: '挂号陪同、检查陪同、取药协助',
|
|
||||||
categoryId: 'escort',
|
|
||||||
price: 168,
|
|
||||||
unit: '次',
|
|
||||||
tags: ['陪诊服务', '安心陪护'],
|
|
||||||
salesText: '已服务320+',
|
|
||||||
imageText: '陪',
|
|
||||||
coverGradient: getServiceGradient('escort'),
|
|
||||||
detailPath: '',
|
|
||||||
bookingPath: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svc-003',
|
|
||||||
title: '慢病随访服务',
|
|
||||||
subtitle: '血压血糖记录、健康建议、定期回访',
|
|
||||||
categoryId: 'chronic',
|
|
||||||
price: 79,
|
|
||||||
unit: '次',
|
|
||||||
tags: ['慢病管理', '健康随访'],
|
|
||||||
salesText: '已服务150+',
|
|
||||||
imageText: '访',
|
|
||||||
coverGradient: getServiceGradient('chronic'),
|
|
||||||
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-003',
|
|
||||||
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-003&mode=booking'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildServiceProductsFromCatalog(catalog: Array<HomeServiceCatalogType>): Array<HomeCareServiceProductType> {
|
function buildServiceProductsFromCatalog(catalog: Array<HomeServiceCatalogType>): Array<HomeCareServiceProductType> {
|
||||||
const result: Array<HomeCareServiceProductType> = []
|
const result: Array<HomeCareServiceProductType> = []
|
||||||
for (let i = 0; i < catalog.length; i++) {
|
for (let i = 0; i < catalog.length; i++) {
|
||||||
@@ -525,14 +463,10 @@ async function loadServiceHomeData(): Promise<void> {
|
|||||||
serviceLoading.value = true
|
serviceLoading.value = true
|
||||||
try {
|
try {
|
||||||
const catalog = await fetchHomeServiceCatalog()
|
const catalog = await fetchHomeServiceCatalog()
|
||||||
if (catalog.length > 0) {
|
allServiceProducts.value = buildServiceProductsFromCatalog(catalog)
|
||||||
allServiceProducts.value = buildServiceProductsFromCatalog(catalog)
|
|
||||||
} else {
|
|
||||||
allServiceProducts.value = buildMockServiceProducts()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载服务首页数据失败', error)
|
console.error('加载服务首页数据失败', error)
|
||||||
allServiceProducts.value = buildMockServiceProducts()
|
allServiceProducts.value = [] as Array<HomeCareServiceProductType>
|
||||||
} finally {
|
} finally {
|
||||||
serviceLoading.value = false
|
serviceLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -620,6 +554,8 @@ const hotProducts = ref<Product[]>([])
|
|||||||
const recommendedProducts = ref<Product[]>([])
|
const recommendedProducts = ref<Product[]>([])
|
||||||
const hotKeywords = ref<string[]>([])
|
const hotKeywords = ref<string[]>([])
|
||||||
const defaultLoadLimit: number = 6
|
const defaultLoadLimit: number = 6
|
||||||
|
const recommendChannelLoadLimit: number = 16
|
||||||
|
const categoryChannelLoadLimit: number = 12
|
||||||
|
|
||||||
// 屏幕尺寸检测
|
// 屏幕尺寸检测
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false)
|
||||||
@@ -922,10 +858,376 @@ function buildSimpleChannelCoverImages(startIndex: number): string[] {
|
|||||||
return covers
|
return covers
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSimpleCategoryChannels(categoryId: string): SimpleCategoryChannel[] {
|
function getRealProductImage(product: Product): string {
|
||||||
return []
|
if (product.main_image_url != null && product.main_image_url !== '') {
|
||||||
|
return product.main_image_url
|
||||||
|
}
|
||||||
|
if (product.images != null && product.images.length > 0 && product.images[0] !== '') {
|
||||||
|
return product.images[0]
|
||||||
|
}
|
||||||
|
if (product.image_url != null && product.image_url !== '') {
|
||||||
|
return product.image_url
|
||||||
|
}
|
||||||
|
return '/static/images/default.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRealSalePrice(product: Product): number {
|
||||||
|
return product.base_price ?? product.price ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getRealMarketPrice(product: Product): number {
|
||||||
|
return product.market_price ?? product.original_price ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function toChannelProduct(product: Product, labelPrefix: string): ChannelProduct {
|
||||||
|
const salePrice = getRealSalePrice(product)
|
||||||
|
const marketPrice = getRealMarketPrice(product)
|
||||||
|
const shortName = product.short_title != null && product.short_title !== ''
|
||||||
|
? product.short_title
|
||||||
|
: (product.name != null && product.name !== '' ? product.name : product.id)
|
||||||
|
return {
|
||||||
|
id: product.id,
|
||||||
|
name: product.name != null && product.name !== '' ? product.name : product.id,
|
||||||
|
shortName,
|
||||||
|
image: getRealProductImage(product),
|
||||||
|
price: salePrice,
|
||||||
|
marketPrice,
|
||||||
|
tag: labelPrefix
|
||||||
|
} as ChannelProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductDiscountScore(product: Product): number {
|
||||||
|
const salePrice = getRealSalePrice(product)
|
||||||
|
const marketPrice = getRealMarketPrice(product)
|
||||||
|
if (marketPrice <= salePrice || marketPrice <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const discountValue = marketPrice - salePrice
|
||||||
|
const discountRate = discountValue / marketPrice
|
||||||
|
return discountRate * 100000 + discountValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductQualityScore(product: Product): number {
|
||||||
|
let score = 0
|
||||||
|
if (product.is_featured == true) {
|
||||||
|
score = score + 100000
|
||||||
|
}
|
||||||
|
if (product.is_hot == true) {
|
||||||
|
score = score + 50000
|
||||||
|
}
|
||||||
|
score = score + (product.sale_count ?? 0)
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductHotScore(product: Product): number {
|
||||||
|
let score = product.sale_count ?? 0
|
||||||
|
if (product.is_hot == true) {
|
||||||
|
score = score + 100000
|
||||||
|
}
|
||||||
|
if (product.is_featured == true) {
|
||||||
|
score = score + 50000
|
||||||
|
}
|
||||||
|
score = score + getProductDiscountScore(product)
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneProductArray(source: Array<Product>): Array<Product> {
|
||||||
|
const result: Array<Product> = []
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
result.push(source[i])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortProductsByScoreDesc(source: Array<Product>, scoreType: string): Array<Product> {
|
||||||
|
const result = cloneProductArray(source)
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
for (let j = i + 1; j < result.length; j++) {
|
||||||
|
let leftScore = 0
|
||||||
|
let rightScore = 0
|
||||||
|
if (scoreType == 'discount') {
|
||||||
|
leftScore = getProductDiscountScore(result[i])
|
||||||
|
rightScore = getProductDiscountScore(result[j])
|
||||||
|
} else if (scoreType == 'quality') {
|
||||||
|
leftScore = getProductQualityScore(result[i])
|
||||||
|
rightScore = getProductQualityScore(result[j])
|
||||||
|
} else {
|
||||||
|
leftScore = getProductHotScore(result[i])
|
||||||
|
rightScore = getProductHotScore(result[j])
|
||||||
|
}
|
||||||
|
if (rightScore > leftScore) {
|
||||||
|
const temp = result[i]
|
||||||
|
result[i] = result[j]
|
||||||
|
result[j] = temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortProductsByPriceAsc(source: Array<Product>): Array<Product> {
|
||||||
|
const result = cloneProductArray(source)
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
for (let j = i + 1; j < result.length; j++) {
|
||||||
|
const leftPrice = getRealSalePrice(result[i])
|
||||||
|
const rightPrice = getRealSalePrice(result[j])
|
||||||
|
if (rightPrice < leftPrice) {
|
||||||
|
const temp = result[i]
|
||||||
|
result[i] = result[j]
|
||||||
|
result[j] = temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterProductsByMode(source: Array<Product>, mode: string): Array<Product> {
|
||||||
|
const result: Array<Product> = []
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
const item = source[i]
|
||||||
|
const salePrice = getRealSalePrice(item)
|
||||||
|
const marketPrice = getRealMarketPrice(item)
|
||||||
|
if (mode == 'discount' && marketPrice > salePrice) {
|
||||||
|
result.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (mode == 'quality' && (item.is_featured == true || item.is_hot == true)) {
|
||||||
|
result.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (mode == 'cheap-9' && salePrice > 0 && salePrice <= 9.9) {
|
||||||
|
result.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (mode == 'cheap-19' && salePrice > 0 && salePrice <= 19.9) {
|
||||||
|
result.push(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (mode == 'live' && (item.is_hot == true || (item.sale_count ?? 0) > 0)) {
|
||||||
|
result.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeUniqueProductLists(first: Array<Product>, second: Array<Product>, third: Array<Product>): Array<Product> {
|
||||||
|
const result: Array<Product> = []
|
||||||
|
const seenIds: Array<string> = []
|
||||||
|
const sources: Array<Array<Product>> = [first, second, third]
|
||||||
|
for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex++) {
|
||||||
|
const source = sources[sourceIndex]
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
const item = source[i]
|
||||||
|
const productId = item.id ?? ''
|
||||||
|
if (productId != '' && seenIds.indexOf(productId) != -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (productId != '') {
|
||||||
|
seenIds.push(productId)
|
||||||
|
}
|
||||||
|
result.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendChannelProducts(source: Array<Product>, result: Array<Product>, selectedIds: Array<string>, desiredCount: number, allowRepeat: boolean): void {
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
if (result.length >= desiredCount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const item = source[i]
|
||||||
|
const productId = item.id ?? ''
|
||||||
|
let existsInResult = false
|
||||||
|
for (let j = 0; j < result.length; j++) {
|
||||||
|
if (result[j].id == productId) {
|
||||||
|
existsInResult = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existsInResult) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!allowRepeat && productId != '' && selectedIds.indexOf(productId) != -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result.push(item)
|
||||||
|
if (!allowRepeat && productId != '') {
|
||||||
|
selectedIds.push(productId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectChannelProducts(primary: Array<Product>, secondary: Array<Product>, fallback: Array<Product>, selectedIds: Array<string>, desiredCount: number): Array<Product> {
|
||||||
|
const result: Array<Product> = []
|
||||||
|
appendChannelProducts(primary, result, selectedIds, desiredCount, false)
|
||||||
|
appendChannelProducts(secondary, result, selectedIds, desiredCount, false)
|
||||||
|
appendChannelProducts(fallback, result, selectedIds, desiredCount, false)
|
||||||
|
appendChannelProducts(primary, result, selectedIds, desiredCount, true)
|
||||||
|
appendChannelProducts(secondary, result, selectedIds, desiredCount, true)
|
||||||
|
appendChannelProducts(fallback, result, selectedIds, desiredCount, true)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChannelFromTemplate(template: MarketingChannel, products: Array<Product>, labelPrefix: string): MarketingChannel {
|
||||||
|
const mappedProducts: Array<ChannelProduct> = []
|
||||||
|
for (let i = 0; i < products.length; i++) {
|
||||||
|
mappedProducts.push(toChannelProduct(products[i], labelPrefix))
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: template.id,
|
||||||
|
title: template.title,
|
||||||
|
subtitle: template.subtitle,
|
||||||
|
badge: template.badge,
|
||||||
|
themeColor: template.themeColor,
|
||||||
|
bgColor: template.bgColor,
|
||||||
|
routeType: template.routeType,
|
||||||
|
layoutType: template.layoutType,
|
||||||
|
products: mappedProducts,
|
||||||
|
moreProducts: mappedProducts
|
||||||
|
} as MarketingChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
function logChannelProducts(channelTitle: string, products: Array<Product>): void {
|
||||||
|
for (let i = 0; i < products.length; i++) {
|
||||||
|
const item = products[i]
|
||||||
|
console.log('[home-channel] ' + channelTitle + ' product:', item.id, item.name ?? '', getRealProductImage(item), getRealSalePrice(item), getRealMarketPrice(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRealRecommendMarketingChannels(products: Array<Product>): MarketingChannel[] {
|
||||||
|
console.log('[home-channel] buildRealRecommendMarketingChannels input count:', products.length)
|
||||||
|
const templates = getRecommendMarketingChannels()
|
||||||
|
if (products.length == 0 || templates.length == 0) {
|
||||||
|
console.log('[home-channel] fallback to mock channel data')
|
||||||
|
return templates
|
||||||
|
}
|
||||||
|
const uniqueProducts = dedupeProducts(products)
|
||||||
|
if (uniqueProducts.length == 0) {
|
||||||
|
console.log('[home-channel] fallback to mock channel data')
|
||||||
|
return templates
|
||||||
|
}
|
||||||
|
const selectedIds: Array<string> = []
|
||||||
|
const discountCandidates = sortProductsByScoreDesc(filterProductsByMode(uniqueProducts, 'discount'), 'discount')
|
||||||
|
const qualityCandidates = sortProductsByScoreDesc(filterProductsByMode(uniqueProducts, 'quality'), 'quality')
|
||||||
|
const cheapCandidates = mergeUniqueProductLists(
|
||||||
|
sortProductsByPriceAsc(filterProductsByMode(uniqueProducts, 'cheap-9')),
|
||||||
|
sortProductsByPriceAsc(filterProductsByMode(uniqueProducts, 'cheap-19')),
|
||||||
|
sortProductsByPriceAsc(uniqueProducts)
|
||||||
|
)
|
||||||
|
const liveCandidates = mergeUniqueProductLists(
|
||||||
|
sortProductsByScoreDesc(filterProductsByMode(uniqueProducts, 'live'), 'hot'),
|
||||||
|
sortProductsByScoreDesc(discountCandidates, 'discount'),
|
||||||
|
sortProductsByScoreDesc(uniqueProducts, 'hot')
|
||||||
|
)
|
||||||
|
const hotFallback = sortProductsByScoreDesc(uniqueProducts, 'hot')
|
||||||
|
const cheapFallback = sortProductsByPriceAsc(uniqueProducts)
|
||||||
|
|
||||||
|
const subsidyProducts = selectChannelProducts(discountCandidates, hotFallback, hotFallback, selectedIds, 2)
|
||||||
|
const qualityProducts = selectChannelProducts(qualityCandidates, hotFallback, hotFallback, selectedIds, 2)
|
||||||
|
const cheapProducts = selectChannelProducts(cheapCandidates, cheapFallback, hotFallback, selectedIds, 2)
|
||||||
|
const liveProducts = selectChannelProducts(liveCandidates, discountCandidates, hotFallback, selectedIds, 2)
|
||||||
|
|
||||||
|
logChannelProducts('百亿补贴', subsidyProducts)
|
||||||
|
logChannelProducts('品质生活', qualityProducts)
|
||||||
|
logChannelProducts('9.9包邮', cheapProducts)
|
||||||
|
logChannelProducts('直播低价', liveProducts)
|
||||||
|
|
||||||
|
const mappedChannels: Array<MarketingChannel> = []
|
||||||
|
for (let i = 0; i < templates.length; i++) {
|
||||||
|
const template = templates[i]
|
||||||
|
if (template.id == 'subsidy') {
|
||||||
|
mappedChannels.push(buildChannelFromTemplate(template, subsidyProducts, '补贴价'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (template.id == 'quality-life') {
|
||||||
|
mappedChannels.push(buildChannelFromTemplate(template, qualityProducts, '实惠'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (template.id == 'cheap-mail') {
|
||||||
|
const cheapMappedProducts: Array<ChannelProduct> = []
|
||||||
|
for (let j = 0; j < cheapProducts.length; j++) {
|
||||||
|
const cheapProduct = cheapProducts[j]
|
||||||
|
const label = getRealSalePrice(cheapProduct) <= 9.9 ? '9.9包邮' : '特价'
|
||||||
|
cheapMappedProducts.push(toChannelProduct(cheapProduct, label))
|
||||||
|
}
|
||||||
|
mappedChannels.push({
|
||||||
|
id: template.id,
|
||||||
|
title: template.title,
|
||||||
|
subtitle: template.subtitle,
|
||||||
|
badge: template.badge,
|
||||||
|
themeColor: template.themeColor,
|
||||||
|
bgColor: template.bgColor,
|
||||||
|
routeType: template.routeType,
|
||||||
|
layoutType: template.layoutType,
|
||||||
|
products: cheapMappedProducts,
|
||||||
|
moreProducts: cheapMappedProducts
|
||||||
|
} as MarketingChannel)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (template.id == 'live-low-price') {
|
||||||
|
mappedChannels.push(buildChannelFromTemplate(template, liveProducts, '直播价'))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mappedChannels.push(template)
|
||||||
|
}
|
||||||
|
return mappedChannels
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSimpleCategoryChannels(categoryId: string, products: Array<Product> = []): SimpleCategoryChannel[] {
|
||||||
|
const dedupedProducts = dedupeProducts(products)
|
||||||
|
if (dedupedProducts.length == 0) {
|
||||||
|
return [] as Array<SimpleCategoryChannel>
|
||||||
|
}
|
||||||
|
const hotProductsForCategory = sortProductsByScoreDesc(dedupedProducts, 'hot')
|
||||||
|
const qualityProductsForCategory = sortProductsByScoreDesc(dedupedProducts, 'quality')
|
||||||
|
const firstChannelCovers: Array<string> = []
|
||||||
|
const secondChannelCovers: Array<string> = []
|
||||||
|
for (let i = 0; i < hotProductsForCategory.length && firstChannelCovers.length < 2; i++) {
|
||||||
|
firstChannelCovers.push(getRealProductImage(hotProductsForCategory[i]))
|
||||||
|
}
|
||||||
|
for (let i = 0; i < qualityProductsForCategory.length && secondChannelCovers.length < 2; i++) {
|
||||||
|
secondChannelCovers.push(getRealProductImage(qualityProductsForCategory[i]))
|
||||||
|
}
|
||||||
|
while (firstChannelCovers.length < 2) {
|
||||||
|
firstChannelCovers.push('/static/images/default.png')
|
||||||
|
}
|
||||||
|
while (secondChannelCovers.length < 2) {
|
||||||
|
secondChannelCovers.push('/static/images/default.png')
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: categoryId + '-rank',
|
||||||
|
title: '热销榜',
|
||||||
|
subtitle: '真实商品热度精选',
|
||||||
|
routeType: 'rank',
|
||||||
|
icon: '热',
|
||||||
|
coverImages: firstChannelCovers,
|
||||||
|
categoryId
|
||||||
|
} as SimpleCategoryChannel,
|
||||||
|
{
|
||||||
|
id: categoryId + '-quality',
|
||||||
|
title: '品质优选',
|
||||||
|
subtitle: '真实好物口碑推荐',
|
||||||
|
routeType: 'quality',
|
||||||
|
icon: '精',
|
||||||
|
coverImages: secondChannelCovers,
|
||||||
|
categoryId
|
||||||
|
} as SimpleCategoryChannel
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCategoryChannelCards(categoryId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const channelResult = await supabaseService.getMedicalMallProductsByCategory(categoryId, 1, categoryChannelLoadLimit)
|
||||||
|
categorySimpleChannels.value = buildSimpleCategoryChannels(categoryId, channelResult.data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[home-channel] 加载分类频道卡片失败', categoryId, error)
|
||||||
|
categorySimpleChannels.value = [] as Array<SimpleCategoryChannel>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildVisibleRecommendChannels(): MarketingChannel[] {
|
function buildVisibleRecommendChannels(): MarketingChannel[] {
|
||||||
const source = getRecommendMarketingChannels()
|
const source = getRecommendMarketingChannels()
|
||||||
const visible: MarketingChannel[] = []
|
const visible: MarketingChannel[] = []
|
||||||
@@ -937,16 +1239,16 @@ function buildVisibleRecommendChannels(): MarketingChannel[] {
|
|||||||
visible.push(channel)
|
visible.push(channel)
|
||||||
}
|
}
|
||||||
return visible
|
return visible
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyChannelDisplay(categoryId: string): void {
|
function applyChannelDisplay(categoryId: string): void {
|
||||||
if (categoryId === 'recommend') {
|
if (categoryId === 'recommend') {
|
||||||
marketingChannels.value = buildVisibleRecommendChannels()
|
marketingChannels.value = [] as Array<MarketingChannel>
|
||||||
categorySimpleChannels.value = []
|
categorySimpleChannels.value = [] as Array<SimpleCategoryChannel>
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
marketingChannels.value = []
|
marketingChannels.value = [] as Array<MarketingChannel>
|
||||||
categorySimpleChannels.value = buildSimpleCategoryChannels(categoryId)
|
categorySimpleChannels.value = [] as Array<SimpleCategoryChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildChannelDetailUrl(channelId: string, routeType: string, categoryId: string): string {
|
function buildChannelDetailUrl(channelId: string, routeType: string, categoryId: string): string {
|
||||||
@@ -1440,11 +1742,18 @@ async function loadHotProducts(page: number, limit: number): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setHotProducts(products)
|
setHotProducts(products)
|
||||||
|
if (currentFeedCategoryId.value === 'recommend' && page <= 1) {
|
||||||
|
marketingChannels.value = buildRealRecommendMarketingChannels(products)
|
||||||
|
}
|
||||||
hasMore.value = result.hasmore
|
hasMore.value = result.hasmore
|
||||||
currentPage.value = page
|
currentPage.value = page
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载热销商品失败:', error)
|
console.error('加载热销商品失败:', error)
|
||||||
hotProducts.value = []
|
hotProducts.value = []
|
||||||
|
if (currentFeedCategoryId.value === 'recommend') {
|
||||||
|
console.log('[home-channel] fallback to mock channel data')
|
||||||
|
marketingChannels.value = buildVisibleRecommendChannels()
|
||||||
|
}
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1476,14 +1785,18 @@ async function loadCategoryGoods(categoryId: string): Promise<void> {
|
|||||||
await syncCategoryLayout(categoryId)
|
await syncCategoryLayout(categoryId)
|
||||||
if (categoryId === 'recommend') {
|
if (categoryId === 'recommend') {
|
||||||
try {
|
try {
|
||||||
const result = await supabaseService.getMedicalMallSmartRecommendations(1, defaultLoadLimit)
|
const result = await supabaseService.getMedicalMallSmartRecommendations(1, recommendChannelLoadLimit)
|
||||||
|
console.log('[home-channel] 推荐商品接口返回数量:', result.data.length)
|
||||||
failedProductImageIds.value = []
|
failedProductImageIds.value = []
|
||||||
setHotProducts(result.data)
|
setHotProducts(result.data)
|
||||||
|
marketingChannels.value = buildRealRecommendMarketingChannels(result.data)
|
||||||
hasMore.value = result.hasmore
|
hasMore.value = result.hasmore
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载热销商品失败:', error)
|
console.error('加载热销商品失败:', error)
|
||||||
hotProducts.value = []
|
hotProducts.value = []
|
||||||
|
console.log('[home-channel] fallback to mock channel data')
|
||||||
|
marketingChannels.value = buildVisibleRecommendChannels()
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1492,10 +1805,12 @@ async function loadCategoryGoods(categoryId: string): Promise<void> {
|
|||||||
const result = await supabaseService.getMedicalMallProductsByCategory(categoryId, 1, defaultLoadLimit)
|
const result = await supabaseService.getMedicalMallProductsByCategory(categoryId, 1, defaultLoadLimit)
|
||||||
failedProductImageIds.value = []
|
failedProductImageIds.value = []
|
||||||
setHotProducts(result.data)
|
setHotProducts(result.data)
|
||||||
|
await loadCategoryChannelCards(categoryId)
|
||||||
hasMore.value = result.hasmore
|
hasMore.value = result.hasmore
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('分类商品加载失败', e)
|
console.error('分类商品加载失败', e)
|
||||||
hotProducts.value = []
|
hotProducts.value = []
|
||||||
|
categorySimpleChannels.value = [] as Array<SimpleCategoryChannel>
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -1520,12 +1835,16 @@ async function refreshHomeCategory(item: CategoryItem): Promise<void> {
|
|||||||
secondaryCategoryDisplay.value = buildSecondaryCategoryDisplay(item.id)
|
secondaryCategoryDisplay.value = buildSecondaryCategoryDisplay(item.id)
|
||||||
applyChannelDisplay(item.id)
|
applyChannelDisplay(item.id)
|
||||||
try {
|
try {
|
||||||
const result = await supabaseService.getMedicalMallSmartRecommendations(1, defaultLoadLimit)
|
const result = await supabaseService.getMedicalMallSmartRecommendations(1, recommendChannelLoadLimit)
|
||||||
|
console.log('[home-channel] 推荐商品接口返回数量:', result.data.length)
|
||||||
setHotProducts(result.data)
|
setHotProducts(result.data)
|
||||||
|
marketingChannels.value = buildRealRecommendMarketingChannels(result.data)
|
||||||
hasMore.value = result.hasmore
|
hasMore.value = result.hasmore
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载推荐商品失败:', error)
|
console.error('加载推荐商品失败:', error)
|
||||||
hotProducts.value = []
|
hotProducts.value = []
|
||||||
|
console.log('[home-channel] fallback to mock channel data')
|
||||||
|
marketingChannels.value = buildVisibleRecommendChannels()
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -1545,10 +1864,12 @@ async function refreshHomeCategory(item: CategoryItem): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const result = await supabaseService.getMedicalMallProductsByCategory(item.id, 1, defaultLoadLimit)
|
const result = await supabaseService.getMedicalMallProductsByCategory(item.id, 1, defaultLoadLimit)
|
||||||
setHotProducts(result.data)
|
setHotProducts(result.data)
|
||||||
|
await loadCategoryChannelCards(item.id)
|
||||||
hasMore.value = result.hasmore
|
hasMore.value = result.hasmore
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('分类商品加载失败', error)
|
console.error('分类商品加载失败', error)
|
||||||
hotProducts.value = []
|
hotProducts.value = []
|
||||||
|
categorySimpleChannels.value = [] as Array<SimpleCategoryChannel>
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -1857,7 +2178,8 @@ const switchSort = (sortId: string) => {
|
|||||||
}
|
}
|
||||||
hasMore.value = true // 重置加载更多状态
|
hasMore.value = true // 重置加载更多状态
|
||||||
// 重新加载热销商品,排序由 Supabase 服务处理
|
// 重新加载热销商品,排序由 Supabase 服务处理
|
||||||
loadHotProducts(1, defaultLoadLimit)
|
const nextLimit = currentFeedCategoryId.value === 'recommend' ? recommendChannelLoadLimit : defaultLoadLimit
|
||||||
|
loadHotProducts(1, nextLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换筛选器
|
// 切换筛选器
|
||||||
@@ -1909,12 +2231,13 @@ const loadMore = async () => {
|
|||||||
showLoadMore.value = true
|
showLoadMore.value = true
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
const pageLimit = currentFeedCategoryId.value === 'recommend' ? recommendChannelLoadLimit : defaultLoadLimit
|
||||||
const nextPage = currentPage.value + 1
|
const nextPage = currentPage.value + 1
|
||||||
const currentCount = hotProducts.value.length
|
const currentCount = hotProducts.value.length
|
||||||
console.log('开始加载更多,当前数量:', currentCount, '页码:', nextPage, '分类:', currentFeedCategoryId.value)
|
console.log('开始加载更多,当前数量:', currentCount, '页码:', nextPage, '分类:', currentFeedCategoryId.value)
|
||||||
|
|
||||||
if (currentFeedCategoryId.value === 'recommend') {
|
if (currentFeedCategoryId.value === 'recommend') {
|
||||||
const result = await fetchSortedProductsPage(nextPage, defaultLoadLimit)
|
const result = await fetchSortedProductsPage(nextPage, pageLimit)
|
||||||
const newProducts = result.data
|
const newProducts = result.data
|
||||||
|
|
||||||
if (newProducts.length == 0) {
|
if (newProducts.length == 0) {
|
||||||
@@ -2025,7 +2348,7 @@ const onScan = (): void => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('扫码失败:', err)
|
console.error('扫码失败:', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,29 +195,12 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="recommend-section">
|
<GuessYouLike
|
||||||
<view class="recommend-header">
|
title="猜你喜欢"
|
||||||
<text class="recommend-title">猜你喜欢</text>
|
:pageSize="8"
|
||||||
<text class="recommend-subtitle">精选推荐,继续逛一逛</text>
|
:loadMoreKey="guessLoadMoreKey"
|
||||||
</view>
|
@productClick="handleGuessProductClick"
|
||||||
<view class="recommend-grid">
|
/>
|
||||||
<view v-for="item in recommendProducts" :key="item.id" class="recommend-card" @click="goToRecommendProduct(item)">
|
|
||||||
<image :src="item.image" class="recommend-image" mode="aspectFill" />
|
|
||||||
<view class="recommend-info">
|
|
||||||
<text class="recommend-name">{{ item.name }}</text>
|
|
||||||
<view class="recommend-meta-row">
|
|
||||||
<text class="recommend-price">¥{{ item.price }}</text>
|
|
||||||
<text class="recommend-tag">{{ item.tag }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="recommend-load-more" @click="loadRecommendProducts(false)">
|
|
||||||
<text v-if="recommendLoading" class="recommend-load-text">正在加载更多...</text>
|
|
||||||
<text v-else-if="!recommendHasMore && recommendInitialized" class="recommend-load-text">没有更多了</text>
|
|
||||||
<text v-else class="recommend-load-text">上拉加载更多</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="profile-bottom-safe"></view>
|
<view class="profile-bottom-safe"></view>
|
||||||
</view>
|
</view>
|
||||||
@@ -229,6 +212,7 @@
|
|||||||
import { UserType } from '@/types/mall-types.uts'
|
import { UserType } from '@/types/mall-types.uts'
|
||||||
import supabaseService from '@/utils/supabaseService.uts'
|
import supabaseService from '@/utils/supabaseService.uts'
|
||||||
import { goToLogin } from '@/utils/utils.uts'
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||||||
|
|
||||||
type UserStatsType = {
|
type UserStatsType = {
|
||||||
points: number
|
points: number
|
||||||
@@ -290,6 +274,9 @@ type PendingReceiptGoodsType = {
|
|||||||
type ModalSuccessResult = { confirm: boolean; cancel: boolean }
|
type ModalSuccessResult = { confirm: boolean; cancel: boolean }
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
GuessYouLike
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userInfo: {
|
userInfo: {
|
||||||
@@ -345,6 +332,7 @@ export default {
|
|||||||
recommendBottomLocked: false,
|
recommendBottomLocked: false,
|
||||||
recommendViewportHeight: 0,
|
recommendViewportHeight: 0,
|
||||||
pageWindowHeight: 0,
|
pageWindowHeight: 0,
|
||||||
|
guessLoadMoreKey: 0,
|
||||||
statusBarHeight: 0,
|
statusBarHeight: 0,
|
||||||
isAndroidApp: false,
|
isAndroidApp: false,
|
||||||
capsuleTop: 0,
|
capsuleTop: 0,
|
||||||
@@ -376,7 +364,6 @@ export default {
|
|||||||
this.initPage()
|
this.initPage()
|
||||||
this.loadUserProfile()
|
this.loadUserProfile()
|
||||||
this.loadOrders()
|
this.loadOrders()
|
||||||
this.loadRecommendProducts(true)
|
|
||||||
|
|
||||||
// 监听订单更新事件
|
// 监听订单更新事件
|
||||||
uni.$on('orderUpdated', this.handleOrderUpdated)
|
uni.$on('orderUpdated', this.handleOrderUpdated)
|
||||||
@@ -474,24 +461,6 @@ export default {
|
|||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
mockRecommendProducts(page: number, pageSize: number): Array<RecommendProductType> {
|
|
||||||
const list: Array<RecommendProductType> = []
|
|
||||||
if (page >= 5) {
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
for (let i: number = 0; i < pageSize; i++) {
|
|
||||||
const index = (page - 1) * pageSize + i + 1
|
|
||||||
list.push({
|
|
||||||
id: 'mock-recommend-' + index,
|
|
||||||
name: '猜你喜欢 推荐商品 ' + index,
|
|
||||||
image: '/static/images/default.png',
|
|
||||||
price: 9.9 + index,
|
|
||||||
tag: '已售' + (100 + index) + '+'
|
|
||||||
} as RecommendProductType)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchRecommendProducts(page: number, pageSize: number): Promise<Array<RecommendProductType>> {
|
async fetchRecommendProducts(page: number, pageSize: number): Promise<Array<RecommendProductType>> {
|
||||||
console.log('[profile推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
console.log('[profile推荐] fetchRecommendProducts 请求 page=', page, 'pageSize=', pageSize)
|
||||||
const result = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
const result = await supabaseService.searchProducts('', page, pageSize, 'sales')
|
||||||
@@ -578,14 +547,7 @@ export default {
|
|||||||
this.recommendInitialized = true
|
this.recommendInitialized = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载推荐商品失败:', e)
|
console.error('加载推荐商品失败:', e)
|
||||||
|
this.recommendHasMore = false
|
||||||
if (reset && this.recommendProducts.length === 0) {
|
|
||||||
const mockList = this.mockRecommendProducts(1, this.recommendPageSize)
|
|
||||||
this.recommendProducts = mockList
|
|
||||||
this.recommendPage = 2
|
|
||||||
this.recommendHasMore = true
|
|
||||||
this.recommendInitialized = true
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.recommendLoading = false
|
this.recommendLoading = false
|
||||||
if (!reset && this.recommendPendingLoad && this.recommendHasMore) {
|
if (!reset && this.recommendPendingLoad && this.recommendHasMore) {
|
||||||
@@ -597,55 +559,20 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onRecommendScrollToLower() {
|
onRecommendScrollToLower() {
|
||||||
console.log('[profile推荐] scrolltolower 触发')
|
this.guessLoadMoreKey = this.guessLoadMoreKey + 1
|
||||||
this.recommendBottomLocked = true
|
|
||||||
this.loadRecommendProducts(false)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRecommendScroll(event: any) {
|
onRecommendScroll(event: any) {
|
||||||
if (this.recommendLoading || !this.recommendHasMore) {
|
return
|
||||||
|
},
|
||||||
|
|
||||||
|
handleGuessProductClick(productId: string) {
|
||||||
|
if (productId == null || productId === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
uni.navigateTo({
|
||||||
const eventObj = this.toRecommendScrollJson(event)
|
url: `/pages/mall/consumer/product-detail?id=${productId}&productId=${productId}`
|
||||||
let detailObj: UTSJSONObject | null = null
|
})
|
||||||
if (eventObj != null) {
|
|
||||||
detailObj = this.toRecommendScrollJson(eventObj.get('detail'))
|
|
||||||
}
|
|
||||||
if (detailObj == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollTop = this.readRecommendScrollMetric(detailObj, 'scrollTop')
|
|
||||||
const scrollHeight = this.readRecommendScrollMetric(detailObj, 'scrollHeight')
|
|
||||||
let clientHeight = this.readRecommendScrollMetric(detailObj, 'clientHeight')
|
|
||||||
|
|
||||||
if (clientHeight <= 0) {
|
|
||||||
clientHeight = this.recommendViewportHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[profile推荐] scroll事件 scrollTop=', scrollTop, 'scrollHeight=', scrollHeight, 'clientHeight=', clientHeight)
|
|
||||||
|
|
||||||
if (scrollHeight <= 0 || clientHeight <= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const distanceToBottom = scrollHeight - scrollTop - clientHeight
|
|
||||||
if (distanceToBottom > 260) {
|
|
||||||
this.recommendBottomLocked = false
|
|
||||||
}
|
|
||||||
if (distanceToBottom <= 180) {
|
|
||||||
console.log('[profile推荐] scroll 兜底触底 distanceToBottom=', distanceToBottom)
|
|
||||||
if (this.recommendBottomLocked) {
|
|
||||||
this.recommendPendingLoad = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.recommendBottomLocked = true
|
|
||||||
this.loadRecommendProducts(false)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[profile推荐] 处理推荐滚动失败', e)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetGuestProfileState() {
|
resetGuestProfileState() {
|
||||||
@@ -1045,9 +972,6 @@ export default {
|
|||||||
const userId = supabaseService.getCurrentUserId()
|
const userId = supabaseService.getCurrentUserId()
|
||||||
if (userId == null || userId === '') {
|
if (userId == null || userId === '') {
|
||||||
this.resetGuestProfileState()
|
this.resetGuestProfileState()
|
||||||
if (!this.recommendInitialized) {
|
|
||||||
this.loadRecommendProducts(true)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,9 +979,6 @@ export default {
|
|||||||
this.loadUserProfile()
|
this.loadUserProfile()
|
||||||
this.loadOrders()
|
this.loadOrders()
|
||||||
this.updateCouponCount() // 更新优惠券数量
|
this.updateCouponCount() // 更新优惠券数量
|
||||||
if (!this.recommendInitialized) {
|
|
||||||
this.loadRecommendProducts(true)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateCouponCount() {
|
async updateCouponCount() {
|
||||||
@@ -1545,13 +1466,14 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
payOrder(order: OrderItemType) {
|
payOrder(order: OrderItemType) {
|
||||||
|
const paymentAmount = order.actual_amount
|
||||||
const userId = supabaseService.getCurrentUserId()
|
const userId = supabaseService.getCurrentUserId()
|
||||||
if (userId == null || userId === '') {
|
if (userId == null || userId === '') {
|
||||||
goToLogin(`/pages/mall/consumer/payment?orderId=${order.id}`)
|
goToLogin(`/pages/mall/consumer/payment?orderId=${order.id}&amount=${paymentAmount}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/mall/consumer/payment?orderId=${order.id}`
|
url: `/pages/mall/consumer/payment?orderId=${order.id}&amount=${paymentAmount}`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -2693,4 +2615,3 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,15 @@
|
|||||||
<input class="input" v-model="regionString" placeholder="省市区县、乡镇等" placeholder-class="placeholder" />
|
<input class="input" v-model="regionString" placeholder="省市区县、乡镇等" placeholder-class="placeholder" />
|
||||||
<text class="arrow-icon">›</text>
|
<text class="arrow-icon">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="location-action-row">
|
||||||
|
<view class="location-action-btn" @click="fillCurrentLocation">
|
||||||
|
<text class="location-action-text">获取当前位置</text>
|
||||||
|
</view>
|
||||||
|
<view class="location-action-btn" @click="pickLocation">
|
||||||
|
<text class="location-action-text">地图选点</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text v-if="locationHint != ''" class="location-hint">{{ locationHint }}</text>
|
||||||
<view class="form-item detail-item">
|
<view class="form-item detail-item">
|
||||||
<text class="label">详细地址</text>
|
<text class="label">详细地址</text>
|
||||||
<textarea class="textarea" v-model="formData.detail" placeholder="街道、楼牌号等" placeholder-class="placeholder" maxlength="100"></textarea>
|
<textarea class="textarea" v-model="formData.detail" placeholder="街道、楼牌号等" placeholder-class="placeholder" maxlength="100"></textarea>
|
||||||
@@ -85,6 +94,9 @@ type Address = {
|
|||||||
detail: string
|
detail: string
|
||||||
isDefault: boolean
|
isDefault: boolean
|
||||||
label?: string
|
label?: string
|
||||||
|
latitude?: number
|
||||||
|
longitude?: number
|
||||||
|
coordinateType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
@@ -92,6 +104,9 @@ const addressId = ref('')
|
|||||||
const regionString = ref('')
|
const regionString = ref('')
|
||||||
const tags = ['家', '公司', '学校']
|
const tags = ['家', '公司', '学校']
|
||||||
const smartInput = ref('')
|
const smartInput = ref('')
|
||||||
|
const locationHint = ref('')
|
||||||
|
const latitude = ref(0)
|
||||||
|
const longitude = ref(0)
|
||||||
|
|
||||||
type AddressForm = {
|
type AddressForm = {
|
||||||
name: string
|
name: string
|
||||||
@@ -120,6 +135,9 @@ const loadAddress = async (id: string) => {
|
|||||||
formData.isDefault = address.is_default
|
formData.isDefault = address.is_default
|
||||||
formData.label = address.label ?? ''
|
formData.label = address.label ?? ''
|
||||||
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||||
|
latitude.value = address.latitude ?? 0
|
||||||
|
longitude.value = address.longitude ?? 0
|
||||||
|
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||||
} else {
|
} else {
|
||||||
// 如果Supabase没有找到,尝试从本地存储加载
|
// 如果Supabase没有找到,尝试从本地存储加载
|
||||||
const storedAddresses = uni.getStorageSync('addresses')
|
const storedAddresses = uni.getStorageSync('addresses')
|
||||||
@@ -133,6 +151,9 @@ const loadAddress = async (id: string) => {
|
|||||||
formData.isDefault = localAddress.isDefault
|
formData.isDefault = localAddress.isDefault
|
||||||
formData.label = localAddress.label ?? ''
|
formData.label = localAddress.label ?? ''
|
||||||
regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()
|
regionString.value = `${localAddress.province} ${localAddress.city} ${localAddress.district}`.trim()
|
||||||
|
latitude.value = localAddress.latitude ?? 0
|
||||||
|
longitude.value = localAddress.longitude ?? 0
|
||||||
|
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +172,9 @@ const loadAddress = async (id: string) => {
|
|||||||
formData.isDefault = address.isDefault
|
formData.isDefault = address.isDefault
|
||||||
formData.label = address.label ?? ''
|
formData.label = address.label ?? ''
|
||||||
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
regionString.value = `${address.province} ${address.city} ${address.district}`.trim()
|
||||||
|
latitude.value = address.latitude ?? 0
|
||||||
|
longitude.value = address.longitude ?? 0
|
||||||
|
locationHint.value = latitude.value != 0 || longitude.value != 0 ? `已定位:${latitude.value}, ${longitude.value}` : ''
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析本地地址数据失败', e)
|
console.error('解析本地地址数据失败', e)
|
||||||
@@ -159,6 +183,42 @@ const loadAddress = async (id: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const applyLocation = (latitudeValue: number, longitudeValue: number, addressText: string, locationName: string) => {
|
||||||
|
latitude.value = latitudeValue
|
||||||
|
longitude.value = longitudeValue
|
||||||
|
locationHint.value = `已定位:${latitudeValue}, ${longitudeValue}`
|
||||||
|
if (addressText != '') {
|
||||||
|
regionString.value = addressText
|
||||||
|
}
|
||||||
|
if (locationName != '' && formData.detail == '') {
|
||||||
|
formData.detail = locationName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fillCurrentLocation = () => {
|
||||||
|
uni.getLocation({
|
||||||
|
type: 'gcj02',
|
||||||
|
success: (res) => {
|
||||||
|
applyLocation(res.latitude, res.longitude, regionString.value, '')
|
||||||
|
uni.showToast({ title: '已获取当前位置', icon: 'success' })
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: '定位失败,请手动输入地址', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickLocation = () => {
|
||||||
|
uni.chooseLocation({
|
||||||
|
success: (res) => {
|
||||||
|
applyLocation(res.latitude, res.longitude, res.address ?? '', res.name ?? '')
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
uni.showToast({ title: '当前环境不支持地图选点,可手动输入', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
if (options == null) return
|
if (options == null) return
|
||||||
const optionsObj = options as UTSJSONObject
|
const optionsObj = options as UTSJSONObject
|
||||||
@@ -216,7 +276,10 @@ const saveAddress = async () => {
|
|||||||
detail_address: formData.detail,
|
detail_address: formData.detail,
|
||||||
postal_code: '', // 如果需要可以添加邮政编码字段
|
postal_code: '', // 如果需要可以添加邮政编码字段
|
||||||
is_default: formData.isDefault,
|
is_default: formData.isDefault,
|
||||||
label: formData.label
|
label: formData.label,
|
||||||
|
latitude: latitude.value,
|
||||||
|
longitude: longitude.value,
|
||||||
|
coordinate_type: 'gcj02'
|
||||||
} as AddAddressParams
|
} as AddAddressParams
|
||||||
|
|
||||||
let success = false
|
let success = false
|
||||||
@@ -231,8 +294,11 @@ const saveAddress = async () => {
|
|||||||
district: district,
|
district: district,
|
||||||
detail_address: formData.detail,
|
detail_address: formData.detail,
|
||||||
postal_code: '',
|
postal_code: '',
|
||||||
is_default: formData.isDefault,
|
is_default: formData.isDefault,
|
||||||
label: formData.label
|
label: formData.label,
|
||||||
|
latitude: latitude.value,
|
||||||
|
longitude: longitude.value,
|
||||||
|
coordinate_type: 'gcj02'
|
||||||
} as UpdateAddressParams
|
} as UpdateAddressParams
|
||||||
success = await supabaseService.updateAddress(addressId.value, updateData)
|
success = await supabaseService.updateAddress(addressId.value, updateData)
|
||||||
} else {
|
} else {
|
||||||
@@ -271,7 +337,10 @@ const saveAddress = async () => {
|
|||||||
district: district,
|
district: district,
|
||||||
detail: formData.detail,
|
detail: formData.detail,
|
||||||
isDefault: formData.isDefault,
|
isDefault: formData.isDefault,
|
||||||
label: formData.label
|
label: formData.label,
|
||||||
|
latitude: latitude.value,
|
||||||
|
longitude: longitude.value,
|
||||||
|
coordinateType: 'gcj02'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -284,7 +353,10 @@ const saveAddress = async () => {
|
|||||||
district: district,
|
district: district,
|
||||||
detail: formData.detail,
|
detail: formData.detail,
|
||||||
isDefault: formData.isDefault,
|
isDefault: formData.isDefault,
|
||||||
label: formData.label
|
label: formData.label,
|
||||||
|
latitude: latitude.value,
|
||||||
|
longitude: longitude.value,
|
||||||
|
coordinateType: 'gcj02'
|
||||||
}
|
}
|
||||||
addresses.push(newAddress)
|
addresses.push(newAddress)
|
||||||
}
|
}
|
||||||
@@ -437,6 +509,38 @@ const deleteAddress = () => {
|
|||||||
border-radius: 16px; /* 详细地址区域也增加圆角 */
|
border-radius: 16px; /* 详细地址区域也增加圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.location-action-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-action-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 40px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-action-btn:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-action-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-hint {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-item .label {
|
.detail-item .label {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ type Address = {
|
|||||||
detail: string
|
detail: string
|
||||||
isDefault: boolean
|
isDefault: boolean
|
||||||
label?: string
|
label?: string
|
||||||
|
latitude?: number
|
||||||
|
longitude?: number
|
||||||
|
coordinateType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const addresses = ref<Address[]>([])
|
const addresses = ref<Address[]>([])
|
||||||
@@ -71,7 +74,10 @@ const loadAddresses = async () => {
|
|||||||
district: item.district,
|
district: item.district,
|
||||||
detail: item.detail_address,
|
detail: item.detail_address,
|
||||||
isDefault: item.is_default,
|
isDefault: item.is_default,
|
||||||
label: ''
|
label: '',
|
||||||
|
latitude: item.latitude ?? 0,
|
||||||
|
longitude: item.longitude ?? 0,
|
||||||
|
coordinateType: item.coordinate_type ?? 'gcj02'
|
||||||
} as Address
|
} as Address
|
||||||
transformedAddresses.push(addr)
|
transformedAddresses.push(addr)
|
||||||
}
|
}
|
||||||
@@ -99,8 +105,8 @@ const loadAddresses = async () => {
|
|||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
if (options == null) return
|
if (options == null) return
|
||||||
const optionsObj = options as UTSJSONObject
|
const selectMode = options['selectMode']
|
||||||
if ((optionsObj.getString('selectMode') ?? '') == 'true') {
|
if (selectMode != null && String(selectMode) == 'true') {
|
||||||
selectionMode.value = true
|
selectionMode.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -166,12 +172,23 @@ const selectAddress = (item: Address) => {
|
|||||||
if (selectionMode.value) {
|
if (selectionMode.value) {
|
||||||
uni.$emit('addressSelected', {
|
uni.$emit('addressSelected', {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
addressId: item.id,
|
||||||
|
userId: '',
|
||||||
recipient_name: item.name,
|
recipient_name: item.name,
|
||||||
|
contactName: item.name,
|
||||||
phone: item.phone,
|
phone: item.phone,
|
||||||
|
contactPhone: item.phone,
|
||||||
province: item.province,
|
province: item.province,
|
||||||
city: item.city,
|
city: item.city,
|
||||||
district: item.district,
|
district: item.district,
|
||||||
detail: item.detail,
|
detail: item.detail,
|
||||||
|
addressDetail: item.detail,
|
||||||
|
houseNumber: item.detail,
|
||||||
|
fullAddress: getFullAddress(item),
|
||||||
|
remark: item.label ?? '',
|
||||||
|
latitude: item.latitude ?? 0,
|
||||||
|
longitude: item.longitude ?? 0,
|
||||||
|
coordinateType: item.coordinateType ?? 'gcj02',
|
||||||
is_default: item.isDefault
|
is_default: item.isDefault
|
||||||
})
|
})
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
|
|||||||
@@ -149,6 +149,13 @@ async function loadData() {
|
|||||||
bookingDays.value = getBookingDayOptions()
|
bookingDays.value = getBookingDayOptions()
|
||||||
bookingSlots.value = getBookingTimeSlots()
|
bookingSlots.value = getBookingTimeSlots()
|
||||||
services.value = await fetchHomeServiceCatalog()
|
services.value = await fetchHomeServiceCatalog()
|
||||||
|
if (services.value.length > 0) {
|
||||||
|
selectService(services.value[0].id, services.value[0].name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedServiceId.value = ''
|
||||||
|
form.serviceId = ''
|
||||||
|
form.serviceName = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectService(serviceId: string, serviceName: string) {
|
function selectService(serviceId: string, serviceName: string) {
|
||||||
@@ -192,6 +199,10 @@ function selectSlot(slotId: string, available: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitApplication() {
|
async function submitApplication() {
|
||||||
|
if (form.serviceId == '' || form.serviceName == '') {
|
||||||
|
uni.showToast({ title: '当前没有可预约的服务项目', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (form.applicantName == '' || form.elderName == '' || form.phone == '' || form.address == '' || form.preferredTime == '') {
|
if (form.applicantName == '' || form.elderName == '' || form.phone == '' || form.address == '' || form.preferredTime == '') {
|
||||||
uni.showToast({ title: '请补全申请信息', icon: 'none' })
|
uni.showToast({ title: '请补全申请信息', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -200,6 +211,10 @@ async function submitApplication() {
|
|||||||
const parsedAge = parseInt(ageText.value)
|
const parsedAge = parseInt(ageText.value)
|
||||||
form.age = isNaN(parsedAge) ? 0 : parsedAge
|
form.age = isNaN(parsedAge) ? 0 : parsedAge
|
||||||
const created = await createHomeServiceApplication(form)
|
const created = await createHomeServiceApplication(form)
|
||||||
|
if (created == null) {
|
||||||
|
uni.showToast({ title: '申请提交失败,请检查登录和预约信息', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
uni.showToast({ title: '申请已提交', icon: 'success' })
|
uni.showToast({ title: '申请已提交', icon: 'success' })
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uv
|
|||||||
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
||||||
import { fetchConsumerAcceptanceDetail, submitConsumerAcceptance } from '@/services/homeServiceService.uts'
|
import { fetchConsumerAcceptanceDetail, submitConsumerAcceptance } from '@/services/homeServiceService.uts'
|
||||||
import { HomeServiceAcceptanceType } from '@/types/home-service.uts'
|
import { HomeServiceAcceptanceType } from '@/types/home-service.uts'
|
||||||
|
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||||
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
|
||||||
const caseId = ref('')
|
const caseId = ref('')
|
||||||
const detail = ref<HomeServiceAcceptanceType | null>(null)
|
const detail = ref<HomeServiceAcceptanceType | null>(null)
|
||||||
@@ -62,17 +64,31 @@ const selectedTags = ref<Array<string>>([])
|
|||||||
const scores = [1, 2, 3, 4, 5]
|
const scores = [1, 2, 3, 4, 5]
|
||||||
const allTags = ['准时上门', '沟通清楚', '动作规范', '记录完整', '需进一步整改']
|
const allTags = ['准时上门', '沟通清楚', '动作规范', '记录完整', '需进一步整改']
|
||||||
|
|
||||||
|
async function ensureLogin(): Promise<boolean> {
|
||||||
|
const user = await getCurrentUser()
|
||||||
|
if (user == null || getCurrentUserId() == '') {
|
||||||
|
goToLogin('/pages/mall/consumer/home-service/feedback?id=' + caseId.value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
const id = options['id']
|
const id = options['id']
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
caseId.value = id as string
|
caseId.value = id as string
|
||||||
fetchConsumerAcceptanceDetail(caseId.value).then((res) => {
|
ensureLogin().then((ok) => {
|
||||||
if (res != null) {
|
if (!ok) {
|
||||||
detail.value = res
|
return
|
||||||
rating.value = res.rating
|
|
||||||
feedback.value = res.feedback
|
|
||||||
selectedTags.value = res.tags.slice(0)
|
|
||||||
}
|
}
|
||||||
|
fetchConsumerAcceptanceDetail(caseId.value).then((res) => {
|
||||||
|
if (res != null) {
|
||||||
|
detail.value = res
|
||||||
|
rating.value = res.rating
|
||||||
|
feedback.value = res.feedback
|
||||||
|
selectedTags.value = res.tags.slice(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -87,6 +103,9 @@ function toggleTag(tag: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitResult(approved: boolean) {
|
async function submitResult(approved: boolean) {
|
||||||
|
if (!(await ensureLogin())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (caseId.value == '' || feedback.value == '') {
|
if (caseId.value == '' || feedback.value == '') {
|
||||||
uni.showToast({ title: '请填写反馈说明', icon: 'none' })
|
uni.showToast({ title: '请填写反馈说明', icon: 'none' })
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
|||||||
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
|
import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
|
||||||
import { fetchConsumerHomeServiceCases, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
import { fetchConsumerHomeServiceCases, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||||
import { HomeServiceCatalogType, HomeServiceCaseType } from '@/types/home-service.uts'
|
import { HomeServiceCatalogType, HomeServiceCaseType } from '@/types/home-service.uts'
|
||||||
|
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||||
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
import {
|
import {
|
||||||
HomeServiceCategoryType,
|
HomeServiceCategoryType,
|
||||||
HomeServiceItemType,
|
HomeServiceItemType,
|
||||||
@@ -125,9 +127,23 @@ async function loadData() {
|
|||||||
categoryGrid.value = getHomeServiceCategories()
|
categoryGrid.value = getHomeServiceCategories()
|
||||||
promoCards.value = getHomeServicePromoCards()
|
promoCards.value = getHomeServicePromoCards()
|
||||||
services.value = await fetchHomeServiceCatalog()
|
services.value = await fetchHomeServiceCatalog()
|
||||||
|
const user = await getCurrentUser()
|
||||||
|
if (user == null || getCurrentUserId() == '') {
|
||||||
|
cases.value = [] as Array<HomeServiceCaseType>
|
||||||
|
return
|
||||||
|
}
|
||||||
cases.value = await fetchConsumerHomeServiceCases()
|
cases.value = await fetchConsumerHomeServiceCases()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureLogin(redirectUrl: string): Promise<boolean> {
|
||||||
|
const user = await getCurrentUser()
|
||||||
|
if (user == null || getCurrentUserId() == '') {
|
||||||
|
goToLogin(redirectUrl)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function switchCategory(categoryId: string) {
|
function switchCategory(categoryId: string) {
|
||||||
selectedCategory.value = categoryId
|
selectedCategory.value = categoryId
|
||||||
}
|
}
|
||||||
@@ -141,11 +157,21 @@ function goDetail(serviceId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goBooking(serviceId: string) {
|
function goBooking(serviceId: string) {
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking' })
|
ensureLogin('/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking').then((ok) => {
|
||||||
|
if (!ok) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url: '/pages/mall/consumer/home-service/service-detail?id=' + serviceId + '&mode=booking' })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function goOrderDetail(caseId: string) {
|
function goOrderDetail(caseId: string) {
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + caseId })
|
ensureLogin('/pages/mall/consumer/home-service/order-detail?id=' + caseId).then((ok) => {
|
||||||
|
if (!ok) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + caseId })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function goDetailByName(serviceName: string) {
|
function goDetailByName(serviceName: string) {
|
||||||
|
|||||||
@@ -59,7 +59,17 @@
|
|||||||
</view>
|
</view>
|
||||||
</ServicePanel>
|
</ServicePanel>
|
||||||
|
|
||||||
<ServicePanel title="服务过程" subtitle="当前以 mock 时间线展示预约受理、派单和上门过程。">
|
<ServicePanel title="服务过程" subtitle="基于真实状态日志展示预约受理、派单、上门与验收进度。">
|
||||||
|
<ServiceInfoList
|
||||||
|
:items="[
|
||||||
|
{ label: '签到时间:', value: detail.checkinTime != '' ? detail.checkinTime : '暂未签到' },
|
||||||
|
{ label: '签到地点:', value: detail.checkinAddress != '' ? detail.checkinAddress : '暂未记录' },
|
||||||
|
{ label: '开始服务:', value: detail.serviceStartedAt != '' ? detail.serviceStartedAt : '暂未开始' },
|
||||||
|
{ label: '完成服务:', value: detail.serviceFinishedAt != '' ? detail.serviceFinishedAt : '暂未完成' },
|
||||||
|
{ label: '执行摘要:', value: detail.executionSummary != '' ? detail.executionSummary : '服务人员暂未提交执行摘要' },
|
||||||
|
{ label: '证据数量:', value: detail.evidenceCount > 0 ? String(detail.evidenceCount) + ' 份' : '暂未上传' }
|
||||||
|
]"
|
||||||
|
></ServiceInfoList>
|
||||||
<ServiceTimeline :items="detail.timeline"></ServiceTimeline>
|
<ServiceTimeline :items="detail.timeline"></ServiceTimeline>
|
||||||
</ServicePanel>
|
</ServicePanel>
|
||||||
|
|
||||||
@@ -82,14 +92,29 @@ import ServiceStatusTag from '@/components/homeService/ServiceStatusTag.uvue'
|
|||||||
import ServiceTimeline from '@/components/homeService/ServiceTimeline.uvue'
|
import ServiceTimeline from '@/components/homeService/ServiceTimeline.uvue'
|
||||||
import { fetchConsumerHomeServiceCaseDetail } from '@/services/homeServiceService.uts'
|
import { fetchConsumerHomeServiceCaseDetail } from '@/services/homeServiceService.uts'
|
||||||
import { HomeServiceCaseType } from '@/types/home-service.uts'
|
import { HomeServiceCaseType } from '@/types/home-service.uts'
|
||||||
|
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||||
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
|
||||||
const caseId = ref('')
|
const caseId = ref('')
|
||||||
const detail = ref<HomeServiceCaseType | null>(null)
|
const detail = ref<HomeServiceCaseType | null>(null)
|
||||||
|
|
||||||
|
async function ensureLogin(): Promise<boolean> {
|
||||||
|
const user = await getCurrentUser()
|
||||||
|
if (user == null || getCurrentUserId() == '') {
|
||||||
|
goToLogin('/pages/mall/consumer/home-service/order-detail?id=' + caseId.value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
if (caseId.value == '') {
|
if (caseId.value == '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!(await ensureLogin())) {
|
||||||
|
detail.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
detail.value = await fetchConsumerHomeServiceCaseDetail(caseId.value)
|
detail.value = await fetchConsumerHomeServiceCaseDetail(caseId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +122,12 @@ function goFeedback() {
|
|||||||
if (caseId.value == '') {
|
if (caseId.value == '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/feedback?id=' + caseId.value })
|
ensureLogin().then((ok) => {
|
||||||
|
if (!ok) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url: '/pages/mall/consumer/home-service/feedback?id=' + caseId.value })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function bookAgain() {
|
function bookAgain() {
|
||||||
|
|||||||
@@ -28,12 +28,20 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<ServicePanel title="Step1 服务地址" subtitle="确认上门地址、楼层与入户条件。">
|
<ServicePanel title="Step1 服务地址" subtitle="确认上门地址、楼层与入户条件。">
|
||||||
<view class="form-item">
|
<view class="service-address-card" @click="selectAddress">
|
||||||
<text class="label">上门地址</text>
|
<view class="service-address-main">
|
||||||
<view class="value-card" @click="selectAddress">
|
<text class="service-address-title">服务地址</text>
|
||||||
<text class="value-card-text">{{ addressLineText }}</text>
|
<view v-if="selectedAddress != null" class="service-address-detail">
|
||||||
<text class="value-card-action">点击更换</text>
|
<view class="service-address-contact-row">
|
||||||
|
<text class="service-address-contact">{{ selectedAddress.contactName }}</text>
|
||||||
|
<text class="service-address-phone">{{ getSelectedAddressPhone() }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="service-address-full">{{ selectedAddress.fullAddress }}</text>
|
||||||
|
<text v-if="selectedAddress.remark != ''" class="service-address-remark">备注:{{ selectedAddress.remark }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-else class="service-address-placeholder">请选择服务地址</text>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="service-address-arrow">></text>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-grid">
|
<view class="form-grid">
|
||||||
<view class="form-grid-item">
|
<view class="form-grid-item">
|
||||||
@@ -271,11 +279,13 @@
|
|||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import ServicePageHeader from '@/components/homeService/ServicePageHeader.uvue'
|
import ServicePageHeader from '@/components/homeService/ServicePageHeader.uvue'
|
||||||
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
|
||||||
import { createHomeServiceApplication, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
import { createHomeServiceApplication, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||||
import { HomeServiceApplicationDraftType, HomeServiceCatalogType } from '@/types/home-service.uts'
|
import { HomeServiceApplicationDraftType, HomeServiceCatalogType, HomeServiceSelectedAddressType } from '@/types/home-service.uts'
|
||||||
|
import { getCurrentUser, getCurrentUserId } from '@/utils/store.uts'
|
||||||
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
import {
|
import {
|
||||||
BookingDayOptionType,
|
BookingDayOptionType,
|
||||||
BookingTimeSlotType,
|
BookingTimeSlotType,
|
||||||
@@ -390,6 +400,7 @@ const servicePackages = ref<Array<ServicePackageOptionType>>([])
|
|||||||
|
|
||||||
const selectedDayId = ref('day-1')
|
const selectedDayId = ref('day-1')
|
||||||
const selectedSlotId = ref('slot-1')
|
const selectedSlotId = ref('slot-1')
|
||||||
|
const selectedAddress = ref<HomeServiceSelectedAddressType | null>(null)
|
||||||
|
|
||||||
const careAddress = ref(defaultCareAddress.address)
|
const careAddress = ref(defaultCareAddress.address)
|
||||||
const careDetailAddress = ref(defaultCareAddress.detailAddress)
|
const careDetailAddress = ref(defaultCareAddress.detailAddress)
|
||||||
@@ -499,10 +510,56 @@ function buildDefaultPackages(currentServiceId: string, basePrice: number, durat
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addressLineText = computed((): string => {
|
const addressLineText = computed((): string => {
|
||||||
|
if (selectedAddress.value != null && selectedAddress.value.fullAddress != '') {
|
||||||
|
return selectedAddress.value.fullAddress
|
||||||
|
}
|
||||||
const fullAddress = careAddress.value + ' ' + careDetailAddress.value
|
const fullAddress = careAddress.value + ' ' + careDetailAddress.value
|
||||||
return fullAddress.trim() != '' ? fullAddress : '请选择上门服务地址'
|
return fullAddress.trim() != '' ? fullAddress : '请选择服务地址'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function applySelectedAddress(address: HomeServiceSelectedAddressType | null): void {
|
||||||
|
selectedAddress.value = address
|
||||||
|
if (address == null) {
|
||||||
|
careAddress.value = ''
|
||||||
|
careDetailAddress.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const locationAddress = address.locationAddress != null && address.locationAddress != '' ? address.locationAddress : address.addressDetail
|
||||||
|
const doorNo = address.doorNo != null && address.doorNo != '' ? address.doorNo : address.houseNumber
|
||||||
|
careAddress.value = locationAddress
|
||||||
|
careDetailAddress.value = doorNo
|
||||||
|
if (contactName.value == '') {
|
||||||
|
contactName.value = address.contactName
|
||||||
|
}
|
||||||
|
const phoneText = address.phone != null && address.phone != '' ? address.phone : address.contactPhone
|
||||||
|
if (contactPhone.value == '') {
|
||||||
|
contactPhone.value = phoneText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedAddressPhone(): string {
|
||||||
|
if (selectedAddress.value == null) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (selectedAddress.value.phone != null && selectedAddress.value.phone != '') {
|
||||||
|
return selectedAddress.value.phone
|
||||||
|
}
|
||||||
|
return selectedAddress.value.contactPhone
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCachedSelectedAddress(): void {
|
||||||
|
try {
|
||||||
|
const cachedAddress = uni.getStorageSync('hss_selected_service_address') as HomeServiceSelectedAddressType | null
|
||||||
|
if (cachedAddress != null && cachedAddress.fullAddress != '') {
|
||||||
|
applySelectedAddress(cachedAddress)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取服务地址缓存失败', error)
|
||||||
|
}
|
||||||
|
applySelectedAddress(null)
|
||||||
|
}
|
||||||
|
|
||||||
const selectedTimeText = computed((): string => {
|
const selectedTimeText = computed((): string => {
|
||||||
let selectedDayLabel = ''
|
let selectedDayLabel = ''
|
||||||
for (let i = 0; i < bookingDays.value.length; i++) {
|
for (let i = 0; i < bookingDays.value.length; i++) {
|
||||||
@@ -611,9 +668,20 @@ function buildDemandSummary(): string {
|
|||||||
+ ',服务需求:' + selectedNeeds
|
+ ',服务需求:' + selectedNeeds
|
||||||
+ ',详细需求:' + demandDetailText.value
|
+ ',详细需求:' + demandDetailText.value
|
||||||
+ ',紧急联系人:' + emergencyName.value + ' ' + emergencyPhone.value
|
+ ',紧急联系人:' + emergencyName.value + ' ' + emergencyPhone.value
|
||||||
|
+ ',服务地址备注:' + (selectedAddress.value != null ? selectedAddress.value.remark : '')
|
||||||
+ ',备注:' + remarkText.value
|
+ ',备注:' + remarkText.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUnavailableServiceState() {
|
||||||
|
serviceTitle.value = '服务暂未配置'
|
||||||
|
serviceSubtitle.value = '当前服务目录未找到该项目,请稍后再试。'
|
||||||
|
servicePrice.value = 0
|
||||||
|
serviceDuration.value = '待配置'
|
||||||
|
serviceSuitableFor.value = '请联系管理员初始化服务目录。'
|
||||||
|
serviceImageText.value = '服务'
|
||||||
|
servicePackages.value = [] as Array<ServicePackageOptionType>
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
bookingDays.value = getBookingDayOptions()
|
bookingDays.value = getBookingDayOptions()
|
||||||
bookingSlots.value = getBookingTimeSlots()
|
bookingSlots.value = getBookingTimeSlots()
|
||||||
@@ -633,15 +701,7 @@ async function loadData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (matchedService == null) {
|
if (matchedService == null) {
|
||||||
const fallbackItems = getHomeServiceItems(catalog)
|
setUnavailableServiceState()
|
||||||
if (fallbackItems.length > 0) {
|
|
||||||
serviceTitle.value = fallbackItems[0].title
|
|
||||||
serviceSubtitle.value = fallbackItems[0].subtitle
|
|
||||||
servicePrice.value = fallbackItems[0].price
|
|
||||||
serviceSuitableFor.value = fallbackItems[0].suitableFor
|
|
||||||
serviceImageText.value = fallbackItems[0].imageText
|
|
||||||
}
|
|
||||||
servicePackages.value = buildDefaultPackages(serviceId.value, servicePrice.value, serviceDuration.value)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serviceTitle.value = matchedService.name
|
serviceTitle.value = matchedService.name
|
||||||
@@ -656,17 +716,22 @@ async function loadData() {
|
|||||||
servicePackages.value = buildDefaultPackages(serviceId.value, servicePrice.value, serviceDuration.value)
|
servicePackages.value = buildDefaultPackages(serviceId.value, servicePrice.value, serviceDuration.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAddress() {
|
async function ensureLogin(): Promise<boolean> {
|
||||||
if (careAddress.value == '' || careDetailAddress.value == '') {
|
const user = await getCurrentUser()
|
||||||
careAddress.value = defaultCareAddress.address
|
if (user == null || getCurrentUserId() == '') {
|
||||||
careDetailAddress.value = defaultCareAddress.detailAddress
|
goToLogin('/pages/mall/consumer/home-service/service-detail?id=' + serviceId.value + '&mode=booking')
|
||||||
careArea.value = defaultCareAddress.area
|
return false
|
||||||
careFloor.value = defaultCareAddress.floor
|
|
||||||
careHasElevator.value = defaultCareAddress.hasElevator
|
|
||||||
uni.showToast({ title: '已填入示例地址', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/address-list' })
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAddress() {
|
||||||
|
ensureLogin().then((ok) => {
|
||||||
|
if (!ok) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateTo({ url: '/pages/address/address-list' })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAgencyReplace() {
|
function handleAgencyReplace() {
|
||||||
@@ -746,8 +811,15 @@ function selectServicePackage(packageId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitBooking() {
|
async function submitBooking() {
|
||||||
if (careAddress.value == '' || careDetailAddress.value == '') {
|
if (!(await ensureLogin())) {
|
||||||
uni.showToast({ title: '请选择上门服务地址', icon: 'none' })
|
return
|
||||||
|
}
|
||||||
|
if (servicePackages.value.length == 0 || servicePrice.value <= 0) {
|
||||||
|
uni.showToast({ title: '当前服务暂不可预约', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (selectedAddress.value == null) {
|
||||||
|
uni.showToast({ title: '请选择服务地址', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (recipientName.value == '') {
|
if (recipientName.value == '') {
|
||||||
@@ -781,10 +853,15 @@ async function submitBooking() {
|
|||||||
phone: contactPhone.value,
|
phone: contactPhone.value,
|
||||||
address: addressLineText.value,
|
address: addressLineText.value,
|
||||||
preferredTime: selectedTimeText.value,
|
preferredTime: selectedTimeText.value,
|
||||||
demandSummary: buildDemandSummary()
|
demandSummary: buildDemandSummary(),
|
||||||
|
serviceAddressSnapshot: selectedAddress.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const created = await createHomeServiceApplication(draft)
|
const created = await createHomeServiceApplication(draft)
|
||||||
|
if (created == null) {
|
||||||
|
uni.showToast({ title: '预约提交失败,请稍后重试', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
uni.showToast({ title: '预约已提交', icon: 'success' })
|
uni.showToast({ title: '预约已提交', icon: 'success' })
|
||||||
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
|
||||||
}
|
}
|
||||||
@@ -796,12 +873,14 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
const mode = options['mode']
|
const mode = options['mode']
|
||||||
if (mode != null && mode == 'booking') {
|
if (mode != null && mode == 'booking') {
|
||||||
careAddress.value = defaultCareAddress.address
|
|
||||||
careDetailAddress.value = defaultCareAddress.detailAddress
|
|
||||||
careArea.value = defaultCareAddress.area
|
careArea.value = defaultCareAddress.area
|
||||||
}
|
}
|
||||||
loadData()
|
loadData()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
loadCachedSelectedAddress()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -845,6 +924,81 @@ onLoad((options) => {
|
|||||||
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
|
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-address-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border: 1rpx solid rgba(255, 125, 151, 0.16);
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 10rpx 20rpx rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-main {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-contact-row {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-contact,
|
||||||
|
.service-address-phone,
|
||||||
|
.service-address-full,
|
||||||
|
.service-address-remark,
|
||||||
|
.service-address-placeholder,
|
||||||
|
.service-address-arrow {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-contact {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-phone {
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-full {
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-remark {
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-address-arrow {
|
||||||
|
color: #c18b95;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-top-row,
|
.summary-top-row,
|
||||||
.summary-tag-row,
|
.summary-tag-row,
|
||||||
.summary-price-row,
|
.summary-price-row,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<view class="status-title-row">
|
<view class="status-title-row">
|
||||||
<text class="status-emoji">{{ getStatusIcon() }}</text>
|
<text class="status-emoji">{{ getStatusIcon() }}</text>
|
||||||
<text class="status-text">{{ getStatusText() }}</text>
|
<text class="status-text">{{ getStatusText() }}</text>
|
||||||
|
<text v-if="getPendingCountdownText() != ''" class="status-countdown">{{ getPendingCountdownText() }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="status-desc">{{ getStatusDesc() }}</text>
|
<text class="status-desc">{{ getStatusDesc() }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -135,7 +136,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-right">
|
<view class="action-right">
|
||||||
<view v-if="order?.order_status === 1" class="btn-group">
|
<view v-if="order?.order_status === 1 && !isTimeoutOrder()" class="btn-group">
|
||||||
<button class="btn" @click="cancelOrder">取消订单</button>
|
<button class="btn" @click="cancelOrder">取消订单</button>
|
||||||
<button class="btn primary" @click="payOrder">立即支付</button>
|
<button class="btn primary" @click="payOrder">立即支付</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -157,9 +158,11 @@
|
|||||||
<button class="btn primary" @click="goToReview">评价订单</button>
|
<button class="btn primary" @click="goToReview">评价订单</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="order?.order_status === 5" class="btn-group">
|
<view v-if="shouldShowCancelledActions()" class="btn-group">
|
||||||
<button class="btn primary" @click="rePurchase">重新购买</button>
|
<button class="btn" @click="deleteOrder">删除订单</button>
|
||||||
</view>
|
<button class="btn" @click="viewSimilar">看相似</button>
|
||||||
|
<button class="btn primary" @click="rePurchase">再次购买</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -167,16 +170,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
import { onBackPress, onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
||||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||||
import { goToLogin } from '@/utils/utils.uts'
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
import supa from '@/components/supadb/aksupainstance.uts'
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
|
import { formatCountdownHMS, getOrderDisplayStatus, getRemainingSeconds, isOrderPayExpired, ORDER_STATUS_CANCELLED, ORDER_STATUS_PENDING, ORDER_TIMEOUT_CANCEL_REASON, PAYMENT_STATUS_TIMEOUT, PAYMENT_STATUS_UNPAID, type OrderStatusSource } from '@/utils/orderStatus.uts'
|
||||||
|
|
||||||
// 定义订单类型
|
// 定义订单类型
|
||||||
type OrderType = {
|
type OrderType = {
|
||||||
order_no: string,
|
order_no: string,
|
||||||
order_status: number,
|
order_status: number,
|
||||||
|
payment_status: number,
|
||||||
|
cancel_reason: string,
|
||||||
|
pay_expire_at: string,
|
||||||
|
consumer_deleted_at: string,
|
||||||
total_amount: number,
|
total_amount: number,
|
||||||
product_amount: number,
|
product_amount: number,
|
||||||
shipping_fee: number,
|
shipping_fee: number,
|
||||||
@@ -221,10 +229,51 @@ const orderItems = ref<OrderItemType[]>([])
|
|||||||
const shopName = ref('店铺名称')
|
const shopName = ref('店铺名称')
|
||||||
const deliveryAddress = ref<AddressType | null>(null)
|
const deliveryAddress = ref<AddressType | null>(null)
|
||||||
const deliveryInfo = ref<DeliveryInfoType | null>(null)
|
const deliveryInfo = ref<DeliveryInfoType | null>(null)
|
||||||
|
const nowTick = ref<number>(Date.now())
|
||||||
|
let detailTicker = 0
|
||||||
|
|
||||||
|
const toOrderStatusSource = (): OrderStatusSource | null => {
|
||||||
|
const currentOrder = order.value
|
||||||
|
if (currentOrder == null) return null
|
||||||
|
return {
|
||||||
|
order_status: currentOrder.order_status,
|
||||||
|
payment_status: currentOrder.payment_status,
|
||||||
|
pay_expire_at: currentOrder.pay_expire_at,
|
||||||
|
created_at: currentOrder.created_at,
|
||||||
|
cancel_reason: currentOrder.cancel_reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTimeoutOrder = (): boolean => {
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source == null) return false
|
||||||
|
return isOrderPayExpired(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPendingCountdownText = (): string => {
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source == null) return ''
|
||||||
|
const currentTick = nowTick.value
|
||||||
|
if (currentTick < 0) return ''
|
||||||
|
if (getOrderDisplayStatus(source) != 'pending') return ''
|
||||||
|
return formatCountdownHMS(getRemainingSeconds(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldShowCancelledActions = (): boolean => {
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source == null) return false
|
||||||
|
return getOrderDisplayStatus(source) == 'cancelled'
|
||||||
|
}
|
||||||
|
|
||||||
// 辅助函数 - 必须在调用前定义
|
// 辅助函数 - 必须在调用前定义
|
||||||
const getStatusText = (): string => {
|
const getStatusText = (): string => {
|
||||||
const status = order.value?.order_status ?? 0
|
const status = order.value?.order_status ?? 0
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source != null) {
|
||||||
|
const displayStatus = getOrderDisplayStatus(source)
|
||||||
|
if (displayStatus == 'pending') return '待付款'
|
||||||
|
if (displayStatus == 'cancelled') return '已取消'
|
||||||
|
}
|
||||||
if (status == 1) return '待付款'
|
if (status == 1) return '待付款'
|
||||||
if (status == 2) return '待发货'
|
if (status == 2) return '待发货'
|
||||||
if (status == 3) return '待收货'
|
if (status == 3) return '待收货'
|
||||||
@@ -237,6 +286,20 @@ const getStatusText = (): string => {
|
|||||||
|
|
||||||
const getStatusDesc = (): string => {
|
const getStatusDesc = (): string => {
|
||||||
const status = order.value?.order_status ?? 0
|
const status = order.value?.order_status ?? 0
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source != null) {
|
||||||
|
const displayStatus = getOrderDisplayStatus(source)
|
||||||
|
if (displayStatus == 'pending') {
|
||||||
|
return '请在 ' + getPendingCountdownText() + ' 内支付'
|
||||||
|
}
|
||||||
|
if (displayStatus == 'cancelled') {
|
||||||
|
const currentReason = order.value?.cancel_reason ?? ''
|
||||||
|
if (currentReason.indexOf('超时') >= 0) {
|
||||||
|
return '订单超时未支付,已自动取消'
|
||||||
|
}
|
||||||
|
return '订单已取消'
|
||||||
|
}
|
||||||
|
}
|
||||||
if (status == 1) return '请尽快完成支付'
|
if (status == 1) return '请尽快完成支付'
|
||||||
if (status == 2) return '商家正在打包商品'
|
if (status == 2) return '商家正在打包商品'
|
||||||
if (status == 3) return '商品正在赶往您的地址'
|
if (status == 3) return '商品正在赶往您的地址'
|
||||||
@@ -249,6 +312,7 @@ const getStatusDesc = (): string => {
|
|||||||
|
|
||||||
const getStatusIcon = (): string => {
|
const getStatusIcon = (): string => {
|
||||||
const status = order.value?.order_status ?? 0
|
const status = order.value?.order_status ?? 0
|
||||||
|
if (shouldShowCancelledActions()) return '⏰'
|
||||||
if (status === 1) return '💳'
|
if (status === 1) return '💳'
|
||||||
if (status === 2) return '📦'
|
if (status === 2) return '📦'
|
||||||
if (status === 3) return '🚚'
|
if (status === 3) return '🚚'
|
||||||
@@ -257,8 +321,14 @@ const getStatusIcon = (): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getStatusClass = (): string => {
|
const getStatusClass = (): string => {
|
||||||
|
const source = toOrderStatusSource()
|
||||||
|
if (source != null) {
|
||||||
|
const displayStatus = getOrderDisplayStatus(source)
|
||||||
|
if (displayStatus == 'pending') return 'status-pending'
|
||||||
|
if (displayStatus == 'cancelled') return 'status-cancelled'
|
||||||
|
}
|
||||||
const status = order.value?.order_status ?? 0
|
const status = order.value?.order_status ?? 0
|
||||||
return `status-${status}`
|
return 'status-' + status
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFullAddress = (addr: any): string => {
|
const getFullAddress = (addr: any): string => {
|
||||||
@@ -389,6 +459,10 @@ const loadOrderDetail = async () => {
|
|||||||
order.value = {
|
order.value = {
|
||||||
order_no: (dataObj.get('order_no') ?? '') as string,
|
order_no: (dataObj.get('order_no') ?? '') as string,
|
||||||
order_status: (dataObj.get('order_status') ?? 1) as number,
|
order_status: (dataObj.get('order_status') ?? 1) as number,
|
||||||
|
payment_status: (dataObj.get('payment_status') ?? 1) as number,
|
||||||
|
cancel_reason: (dataObj.get('cancel_reason') ?? '') as string,
|
||||||
|
pay_expire_at: (dataObj.get('pay_expire_at') ?? '') as string,
|
||||||
|
consumer_deleted_at: (dataObj.get('consumer_deleted_at') ?? '') as string,
|
||||||
total_amount: (dataObj.get('total_amount') ?? 0) as number,
|
total_amount: (dataObj.get('total_amount') ?? 0) as number,
|
||||||
product_amount: (dataObj.get('product_amount') ?? 0) as number,
|
product_amount: (dataObj.get('product_amount') ?? 0) as number,
|
||||||
shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,
|
shipping_fee: (dataObj.get('shipping_fee') ?? 0) as number,
|
||||||
@@ -424,6 +498,15 @@ const loadOrderDetail = async () => {
|
|||||||
orderItems.value.push(orderItem)
|
orderItems.value.push(orderItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (order.value.consumer_deleted_at != '') {
|
||||||
|
order.value = null
|
||||||
|
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
|
||||||
|
}, 600)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const addressRaw = dataObj.get('shipping_address')
|
const addressRaw = dataObj.get('shipping_address')
|
||||||
console.log('[loadOrderDetail] 收货地址数据:', addressRaw)
|
console.log('[loadOrderDetail] 收货地址数据:', addressRaw)
|
||||||
@@ -470,6 +553,7 @@ const loadOrderDetail = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)
|
console.log('[loadOrderDetail] 订单详情加载成功,商品数量:', orderItems.value.length)
|
||||||
|
syncTimeoutState()
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||||
}
|
}
|
||||||
@@ -481,6 +565,35 @@ const loadOrderDetail = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncTimeoutState = (): void => {
|
||||||
|
nowTick.value = Date.now()
|
||||||
|
const currentOrder = order.value
|
||||||
|
if (currentOrder == null) return
|
||||||
|
if (currentOrder.order_status == ORDER_STATUS_PENDING && currentOrder.payment_status == PAYMENT_STATUS_UNPAID && isTimeoutOrder()) {
|
||||||
|
currentOrder.order_status = ORDER_STATUS_CANCELLED
|
||||||
|
currentOrder.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||||
|
if (currentOrder.cancel_reason == '') {
|
||||||
|
currentOrder.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
|
||||||
|
}
|
||||||
|
supabaseService.expireOrder(orderId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDetailTicker = (): void => {
|
||||||
|
if (detailTicker > 0) return
|
||||||
|
syncTimeoutState()
|
||||||
|
detailTicker = setInterval(() => {
|
||||||
|
syncTimeoutState()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopDetailTicker = (): void => {
|
||||||
|
if (detailTicker > 0) {
|
||||||
|
clearInterval(detailTicker)
|
||||||
|
detailTicker = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 动作函数
|
// 动作函数
|
||||||
const contactService = () => {
|
const contactService = () => {
|
||||||
const userId = supabaseService.getCurrentUserId()
|
const userId = supabaseService.getCurrentUserId()
|
||||||
@@ -508,13 +621,49 @@ const contactService = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const payOrder = () => {
|
const payOrder = async () => {
|
||||||
|
if (isTimeoutOrder()) {
|
||||||
|
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
|
||||||
|
loadOrderDetail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const totalAmount = order.value?.total_amount ?? 0
|
const totalAmount = order.value?.total_amount ?? 0
|
||||||
const userId = supabaseService.getCurrentUserId()
|
const userId = supabaseService.getCurrentUserId()
|
||||||
if (userId == null || userId === '') {
|
if (userId == null || userId === '') {
|
||||||
goToLogin(`/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`)
|
goToLogin(`/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const latestOrder = await supabaseService.getOrderDetail(orderId.value)
|
||||||
|
if (latestOrder != null) {
|
||||||
|
const latestObj = JSON.parse(JSON.stringify(latestOrder)) as UTSJSONObject
|
||||||
|
const latestStatus = latestObj.getNumber('order_status') ?? 1
|
||||||
|
const latestPaymentStatus = latestObj.getNumber('payment_status') ?? 1
|
||||||
|
const latestCancelReason = latestObj.getString('cancel_reason') ?? ''
|
||||||
|
const latestPayExpireAt = latestObj.getString('pay_expire_at') ?? ''
|
||||||
|
if (order.value != null) {
|
||||||
|
order.value.order_status = latestStatus
|
||||||
|
order.value.payment_status = latestPaymentStatus
|
||||||
|
order.value.cancel_reason = latestCancelReason
|
||||||
|
order.value.pay_expire_at = latestPayExpireAt
|
||||||
|
}
|
||||||
|
if (isTimeoutOrder()) {
|
||||||
|
await supabaseService.expireOrder(orderId.value)
|
||||||
|
if (order.value != null) {
|
||||||
|
order.value.order_status = ORDER_STATUS_CANCELLED
|
||||||
|
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||||
|
order.value.cancel_reason = ORDER_TIMEOUT_CANCEL_REASON
|
||||||
|
}
|
||||||
|
uni.showToast({ title: '订单已取消,不能继续支付', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (latestStatus != ORDER_STATUS_PENDING || latestPaymentStatus != PAYMENT_STATUS_UNPAID) {
|
||||||
|
uni.showToast({ title: '订单状态已变更,不能继续支付', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
|
url: `/pages/mall/consumer/payment?orderId=${orderId.value}&amount=${totalAmount}`
|
||||||
})
|
})
|
||||||
@@ -522,23 +671,14 @@ const payOrder = () => {
|
|||||||
|
|
||||||
const doCancelOrder = async () => {
|
const doCancelOrder = async () => {
|
||||||
try {
|
try {
|
||||||
const updatePayload = new UTSJSONObject()
|
const result = await supabaseService.cancelOrder(orderId.value)
|
||||||
updatePayload.set('order_status', 5)
|
if (result) {
|
||||||
updatePayload.set('updated_at', new Date().toISOString())
|
|
||||||
|
|
||||||
const result = await supa
|
|
||||||
.from('ml_orders')
|
|
||||||
.update(updatePayload)
|
|
||||||
.eq('id', orderId.value)
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
if (result.error == null) {
|
|
||||||
if (order.value != null) {
|
if (order.value != null) {
|
||||||
order.value.order_status = 5
|
order.value.order_status = ORDER_STATUS_CANCELLED
|
||||||
|
order.value.payment_status = PAYMENT_STATUS_TIMEOUT
|
||||||
}
|
}
|
||||||
uni.showToast({ title: '订单已取消' })
|
uni.showToast({ title: '订单已取消' })
|
||||||
} else {
|
} else {
|
||||||
console.error('[doCancelOrder] 取消订单失败:', result.error)
|
|
||||||
uni.showToast({ title: '取消失败', icon: 'none' })
|
uni.showToast({ title: '取消失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -547,6 +687,34 @@ const doCancelOrder = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteOrder = async () => {
|
||||||
|
uni.showLoading({ title: '删除中...' })
|
||||||
|
try {
|
||||||
|
const success = await supabaseService.softDeleteOrderForConsumer(orderId.value)
|
||||||
|
uni.hideLoading()
|
||||||
|
if (!success) {
|
||||||
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.showToast({ title: '订单已删除', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({ url: '/pages/mall/consumer/orders' })
|
||||||
|
}, 600)
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewSimilar = () => {
|
||||||
|
if (orderItems.value.length == 0) {
|
||||||
|
uni.showToast({ title: '暂无相似商品', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const keyword = orderItems.value[0].product_name != '' ? orderItems.value[0].product_name : '商品'
|
||||||
|
uni.navigateTo({ url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}` })
|
||||||
|
}
|
||||||
|
|
||||||
const cancelOrder = () => {
|
const cancelOrder = () => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
@@ -801,6 +969,21 @@ onLoad((options) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
if (orderId.value != '') {
|
||||||
|
loadOrderDetail()
|
||||||
|
}
|
||||||
|
startDetailTicker()
|
||||||
|
})
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
stopDetailTicker()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
stopDetailTicker()
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -868,12 +1051,26 @@ onLoad((options) => {
|
|||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-countdown {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.status-desc {
|
.status-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-pending {
|
||||||
|
background: linear-gradient(135deg, #ff9000, #ff5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cancelled {
|
||||||
|
background: linear-gradient(135deg, #9aa5b1, #6b7280);
|
||||||
|
}
|
||||||
|
|
||||||
/* 分享免单入口 */
|
/* 分享免单入口 */
|
||||||
.share-free-entry {
|
.share-free-entry {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,27 @@ const orderId = ref('')
|
|||||||
const orderNo = ref('')
|
const orderNo = ref('')
|
||||||
const amount = ref(0)
|
const amount = ref(0)
|
||||||
|
|
||||||
|
const getOptionString = (options: UTSJSONObject, key: string): string => {
|
||||||
|
try {
|
||||||
|
const value = options.getString(key)
|
||||||
|
if (value != null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const normalized = JSON.parse(JSON.stringify(options)) as UTSJSONObject
|
||||||
|
const value = normalized.getString(key)
|
||||||
|
if (value != null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// 定义 loadOrderInfo 函数(必须在 onMounted 之前)
|
// 定义 loadOrderInfo 函数(必须在 onMounted 之前)
|
||||||
const loadOrderInfo = async () => {
|
const loadOrderInfo = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -60,12 +81,12 @@ onLoad((options) => {
|
|||||||
if (options == null) return
|
if (options == null) return
|
||||||
|
|
||||||
const optionsObj = options as UTSJSONObject
|
const optionsObj = options as UTSJSONObject
|
||||||
const orderIdValue = optionsObj.getString('orderId') ?? ''
|
const orderIdValue = getOptionString(optionsObj, 'orderId')
|
||||||
if (orderIdValue != '') {
|
if (orderIdValue != '') {
|
||||||
orderId.value = orderIdValue
|
orderId.value = orderIdValue
|
||||||
orderNo.value = orderIdValue
|
orderNo.value = orderIdValue
|
||||||
|
|
||||||
const amountValue = optionsObj.getString('amount') ?? ''
|
const amountValue = getOptionString(optionsObj, 'amount')
|
||||||
if (amountValue != '') {
|
if (amountValue != '') {
|
||||||
const amountStr = amountValue
|
const amountStr = amountValue
|
||||||
console.log('[payment-success] amountStr:', amountStr)
|
console.log('[payment-success] amountStr:', amountStr)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="search-page">
|
<view class="search-page">
|
||||||
<view class="search-navbar" :style="navbarStyle">
|
<view class="search-navbar" :style="navbarStyle">
|
||||||
<view class="back-btn" @click="goBack">
|
<view class="back-btn" @click="goBack">
|
||||||
<text class="back-icon">‹</text>
|
<text class="back-icon"><</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="jd-search-box">
|
<view class="jd-search-box">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
:value="searchKeyword"
|
:value="searchKeyword"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@confirm="onSearch"
|
@confirm="onSearch"
|
||||||
:placeholder="(placeholderKeyword != null && placeholderKeyword !== '' && searchKeyword === '') ? placeholderKeyword : '请输入商品名称、店铺'"
|
:placeholder="(placeholderKeyword != null && placeholderKeyword !== '' && searchKeyword === '') ? placeholderKeyword : '请输入商品名称、店铺'"
|
||||||
placeholder-class="jd-placeholder"
|
placeholder-class="jd-placeholder"
|
||||||
:focus="autoFocus"
|
:focus="autoFocus"
|
||||||
confirm-type="search"
|
confirm-type="search"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="camera-wrap" @tap.stop="handleCameraSearch">
|
<view class="camera-wrap" @tap.stop="handleCameraSearch">
|
||||||
<text class="camera-icon">📷</text>
|
<text class="camera-icon">拍</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="search-submit-btn" @tap.stop="onSearch">
|
<view class="search-submit-btn" @tap.stop="onSearch">
|
||||||
@@ -32,23 +32,25 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 错误状态(模拟服务器超时) -->
|
<!-- 错误状态 -->
|
||||||
<view v-if="isError" class="error-state" @click="retryLoad">
|
<view v-if="isError" class="error-state" @click="retryLoad">
|
||||||
<view class="error-content">
|
<view class="error-content">
|
||||||
<text class="error-icon">⚠️</text>
|
<text class="error-icon">!</text>
|
||||||
<text class="error-title">加载服务器超时</text>
|
<text class="error-title">加载服务超时</text>
|
||||||
<text class="error-desc">请点击屏幕重试</text>
|
<text class="error-desc">请点击屏幕重试</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 主内容区域:使用 scroll-view 支持安卓端滚动 -->
|
<!-- 涓诲唴瀹瑰尯鍩燂細浣跨敤 scroll-view 鏀寔瀹夊崜绔粴鍔?-->
|
||||||
<scroll-view
|
<scroll-view
|
||||||
v-else
|
v-else
|
||||||
class="main-content"
|
class="main-content"
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
:show-scrollbar="false"
|
:show-scrollbar="false"
|
||||||
>
|
:lower-threshold="120"
|
||||||
<!-- 初始状态(无搜索词) -->
|
@scrolltolower="handleMainScrollToLower"
|
||||||
|
>
|
||||||
|
<!-- 初始状态 -->
|
||||||
<view v-if="searchKeyword == '' && showResults == false">
|
<view v-if="searchKeyword == '' && showResults == false">
|
||||||
<!-- 搜索历史 -->
|
<!-- 搜索历史 -->
|
||||||
<view v-if="searchHistory.length > 0" class="search-history">
|
<view v-if="searchHistory.length > 0" class="search-history">
|
||||||
@@ -97,51 +99,21 @@
|
|||||||
>
|
>
|
||||||
<text class="hot-rank" :class="index < 3 ? 'top-three' : ''">{{ index + 1 }}</text>
|
<text class="hot-rank" :class="index < 3 ? 'top-three' : ''">{{ index + 1 }}</text>
|
||||||
<text class="hot-text">{{ item.keyword }}</text>
|
<text class="hot-text">{{ item.keyword }}</text>
|
||||||
<text v-if="item.hot == true" class="hot-icon">🔥</text>
|
<text v-if="item.hot == true" class="hot-icon">热</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 推荐商品(猜你喜欢) -->
|
<!-- 猜你喜欢 -->
|
||||||
<view class="guess-you-like">
|
<view class="guess-you-like">
|
||||||
<view class="section-header">
|
<GuessYouLike
|
||||||
<view class="title-with-icon">
|
title="猜你喜欢"
|
||||||
<text class="section-icon">✨</text>
|
:pageSize="8"
|
||||||
<text class="section-title">猜你喜欢</text>
|
:loadMoreKey="guessLoadMoreKey"
|
||||||
</view>
|
@productClick="goToProductDetail"
|
||||||
<text class="refresh-btn" @click="refreshGuessList">换一批</text>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="guess-grid">
|
|
||||||
<view
|
|
||||||
v-for="item in recommendList"
|
|
||||||
:key="(item.id != null ? item.id : item.name)"
|
|
||||||
class="guess-item"
|
|
||||||
@click="viewProductDetail(item)"
|
|
||||||
>
|
|
||||||
<image class="guess-img" :src="item.image" mode="aspectFill" />
|
|
||||||
<view v-if="item.cardTags.length > 0" class="card-tags-row">
|
|
||||||
<text v-for="(tag, index) in item.cardTags" :key="item.id + '-guess-tag-' + index" class="card-tag">{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="guess-name" :lines="2">{{ item.name }}</text>
|
|
||||||
<text v-if="item.highlight !== ''" class="card-highlight">{{ item.highlight }}</text>
|
|
||||||
<view v-if="item.serviceTags.length > 0" class="service-tags-row">
|
|
||||||
<text v-for="(tag, index) in item.serviceTags" :key="item.id + '-guess-service-' + index" class="service-tag">{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="guess-bottom">
|
|
||||||
<view class="price-stack">
|
|
||||||
<text class="guess-price">¥{{ item.price }}</text>
|
|
||||||
<text v-if="item.marketPrice !== ''" class="market-price">¥{{ item.marketPrice }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="guess-add-btn" @click.stop="addToCart(item)">
|
|
||||||
<text class="guess-add-icon">+</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<text v-if="item.salesText !== ''" class="card-sales-text">{{ item.salesText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 搜索建议 -->
|
<!-- 搜索建议 -->
|
||||||
<view v-if="searchKeyword != '' && showResults == false" class="search-suggestions">
|
<view v-if="searchKeyword != '' && showResults == false" class="search-suggestions">
|
||||||
@@ -152,7 +124,7 @@
|
|||||||
class="suggestion-item"
|
class="suggestion-item"
|
||||||
@click="selectSuggestion(suggestion)"
|
@click="selectSuggestion(suggestion)"
|
||||||
>
|
>
|
||||||
<view class="suggestion-icon">🔍</view>
|
<view class="suggestion-icon">搜</view>
|
||||||
<text class="suggestion-text">{{ suggestion }}</text>
|
<text class="suggestion-text">{{ suggestion }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -176,7 +148,7 @@
|
|||||||
<image class="shop-logo" :src="shop.logo" mode="aspectFill" />
|
<image class="shop-logo" :src="shop.logo" mode="aspectFill" />
|
||||||
<view class="shop-info">
|
<view class="shop-info">
|
||||||
<text class="shop-name-txt">{{ shop.name }}</text>
|
<text class="shop-name-txt">{{ shop.name }}</text>
|
||||||
<text class="shop-products-txt">共{{ shop.productCount }}件商品</text>
|
<text class="shop-products-txt">共 {{ shop.productCount }} 件商品</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -195,13 +167,13 @@
|
|||||||
class="filter-tab"
|
class="filter-tab"
|
||||||
:class="{ active: activeSort === 'sales' }"
|
:class="{ active: activeSort === 'sales' }"
|
||||||
@click="switchSort('sales')"
|
@click="switchSort('sales')"
|
||||||
>销量</text>
|
>销量</text>
|
||||||
<text
|
<text
|
||||||
class="filter-tab"
|
class="filter-tab"
|
||||||
:class="{ active: activeSort === 'price' }"
|
:class="{ active: activeSort === 'price' }"
|
||||||
@click="switchSort('price')"
|
@click="switchSort('price')"
|
||||||
>
|
>
|
||||||
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
|
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -235,14 +207,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空结果 - 仅在非加载状态且无结果时显示 -->
|
<!-- 空结果 -->
|
||||||
<view v-if="!loading && searchResults.length === 0" class="empty-result">
|
<view v-if="!loading && searchResults.length === 0" class="empty-result">
|
||||||
<text class="empty-icon">🤔</text>
|
<text class="empty-icon">!</text>
|
||||||
<text class="empty-text">未找到相关商品</text>
|
<text class="empty-text">未找到相关商品</text>
|
||||||
<text class="empty-sub">换个关键词试试吧</text>
|
<text class="empty-sub">换个关键词试试吧</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载更多/加载中 - 在加载状态或有更多数据时显示 -->
|
<!-- 加载更多 -->
|
||||||
<view v-if="loading" class="loading-more">
|
<view v-if="loading" class="loading-more">
|
||||||
<view class="loading-spinner"></view>
|
<view class="loading-spinner"></view>
|
||||||
<text class="loading-text">加载中...</text>
|
<text class="loading-text">加载中...</text>
|
||||||
@@ -252,41 +224,14 @@
|
|||||||
<text class="no-more-text">--- 到底了 ---</text>
|
<text class="no-more-text">--- 到底了 ---</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="searchResults.length > 0 && recommendList.length > 0" class="guess-you-like" style="margin-top: 16rpx;">
|
<view v-if="searchResults.length > 0" class="guess-you-like" style="margin-top: 16rpx;">
|
||||||
<view class="section-header">
|
<GuessYouLike
|
||||||
<view class="title-with-icon">
|
title="猜你喜欢"
|
||||||
<text class="section-icon">✨</text>
|
:pageSize="8"
|
||||||
<text class="section-title">猜你喜欢</text>
|
:excludeProductIds="searchResultProductIds"
|
||||||
</view>
|
:loadMoreKey="guessLoadMoreKey"
|
||||||
</view>
|
@productClick="goToProductDetail"
|
||||||
<view class="guess-grid">
|
/>
|
||||||
<view
|
|
||||||
v-for="item in recommendList"
|
|
||||||
:key="(item.id != null ? item.id : '') + '_rec'"
|
|
||||||
class="guess-item"
|
|
||||||
@click="viewProductDetail(item)"
|
|
||||||
>
|
|
||||||
<image class="guess-img" :src="item.image" mode="aspectFill" />
|
|
||||||
<view v-if="item.cardTags.length > 0" class="card-tags-row">
|
|
||||||
<text v-for="(tag, index) in item.cardTags" :key="item.id + '-rec-tag-' + index" class="card-tag">{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="guess-name" :lines="2">{{ item.name }}</text>
|
|
||||||
<text v-if="item.highlight !== ''" class="card-highlight">{{ item.highlight }}</text>
|
|
||||||
<view v-if="item.serviceTags.length > 0" class="service-tags-row">
|
|
||||||
<text v-for="(tag, index) in item.serviceTags" :key="item.id + '-rec-service-' + index" class="service-tag">{{ tag }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="guess-bottom">
|
|
||||||
<view class="price-stack">
|
|
||||||
<text class="guess-price">¥{{ item.price }}</text>
|
|
||||||
<text v-if="item.marketPrice !== ''" class="market-price">¥{{ item.marketPrice }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="guess-add-btn" @click.stop="addToCart(item)">
|
|
||||||
<text class="guess-add-icon">+</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<text v-if="item.salesText !== ''" class="card-sales-text">{{ item.salesText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -302,6 +247,7 @@ import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
|
|||||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||||
import type { Product } from '@/utils/supabaseService.uts'
|
import type { Product } from '@/utils/supabaseService.uts'
|
||||||
import { goToLogin } from '@/utils/utils.uts'
|
import { goToLogin } from '@/utils/utils.uts'
|
||||||
|
import GuessYouLike from '@/components/mall/GuessYouLike/GuessYouLike.uvue'
|
||||||
|
|
||||||
// 状态定义
|
// 状态定义
|
||||||
const statusBarHeight = ref(0)
|
const statusBarHeight = ref(0)
|
||||||
@@ -316,6 +262,7 @@ const loading = ref(false)
|
|||||||
const hasMore = ref(true)
|
const hasMore = ref(true)
|
||||||
const isError = ref(false)
|
const isError = ref(false)
|
||||||
const autoFocus = ref(true)
|
const autoFocus = ref(true)
|
||||||
|
const guessLoadMoreKey = ref<number>(0)
|
||||||
|
|
||||||
const navbarStyle = computed(() => {
|
const navbarStyle = computed(() => {
|
||||||
const top = navBarTop.value > 0 ? navBarTop.value : statusBarHeight.value
|
const top = navBarTop.value > 0 ? navBarTop.value : statusBarHeight.value
|
||||||
@@ -330,7 +277,6 @@ const handleCameraSearch = () => {
|
|||||||
|
|
||||||
const activeSort = ref('default') // 当前排序方式: default, sales, price
|
const activeSort = ref('default') // 当前排序方式: default, sales, price
|
||||||
const priceSortAsc = ref(false) // 价格排序是否为升序
|
const priceSortAsc = ref(false) // 价格排序是否为升序
|
||||||
|
|
||||||
type HotSearchItemType = {
|
type HotSearchItemType = {
|
||||||
keyword: string
|
keyword: string
|
||||||
hot: boolean
|
hot: boolean
|
||||||
@@ -436,21 +382,21 @@ const guessList = ref<Array<GuessItemType>>([])
|
|||||||
const allGuessItems = ref<Array<GuessItemType>>([])
|
const allGuessItems = ref<Array<GuessItemType>>([])
|
||||||
|
|
||||||
const DEFAULT_HOT_KEYWORDS: Array<string> = [
|
const DEFAULT_HOT_KEYWORDS: Array<string> = [
|
||||||
'大疆neo2',
|
'澶х枂neo2',
|
||||||
'iPhone 15 Pro',
|
'iPhone 15 Pro',
|
||||||
'Nike Air Max 270',
|
'Nike Air Max 270',
|
||||||
'厨具',
|
'厨具',
|
||||||
'老干妈',
|
'老干妈',
|
||||||
'钢化膜',
|
'钢化膜',
|
||||||
'手机壳',
|
'手机壳',
|
||||||
'零食坚果',
|
'零食坚果',
|
||||||
'新鲜水果',
|
'新鲜水果',
|
||||||
'液态硅胶壳',
|
'液态硅胶壳',
|
||||||
'充电宝',
|
'充电宝',
|
||||||
'蓝牙耳机'
|
'蓝牙耳机'
|
||||||
]
|
]
|
||||||
|
|
||||||
// 推荐商品区(用于 “猜你喜欢/推荐商品”)
|
// 推荐商品区
|
||||||
const recommendList = ref<Array<GuessItemType>>([])
|
const recommendList = ref<Array<GuessItemType>>([])
|
||||||
const recommendPool = ref<Array<GuessItemType>>([])
|
const recommendPool = ref<Array<GuessItemType>>([])
|
||||||
const recommendPage = ref(0)
|
const recommendPage = ref(0)
|
||||||
@@ -459,6 +405,16 @@ const recommendAppendSize = ref(20)
|
|||||||
const loadingRecommend = ref(false)
|
const loadingRecommend = ref(false)
|
||||||
const searchResults = ref<Array<SearchResultType>>([])
|
const searchResults = ref<Array<SearchResultType>>([])
|
||||||
const searchShopResults = ref<Array<ShopResultType>>([])
|
const searchShopResults = ref<Array<ShopResultType>>([])
|
||||||
|
const searchResultProductIds = computed((): Array<string> => {
|
||||||
|
const ids: Array<string> = []
|
||||||
|
for (let i = 0; i < searchResults.value.length; i++) {
|
||||||
|
const id = searchResults.value[i].id
|
||||||
|
if (id !== '' && ids.indexOf(id) < 0) {
|
||||||
|
ids.push(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
})
|
||||||
|
|
||||||
const SEARCH_HISTORY_KEY = 'consumer_search_history'
|
const SEARCH_HISTORY_KEY = 'consumer_search_history'
|
||||||
|
|
||||||
@@ -504,8 +460,8 @@ const MAX_EXPANDED_COUNT = 24
|
|||||||
|
|
||||||
const clearHistory = () => {
|
const clearHistory = () => {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定清空搜索历史吗?',
|
content: '确定清空搜索历史吗?',
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
searchHistory.value = []
|
searchHistory.value = []
|
||||||
@@ -525,8 +481,6 @@ const deleteHistoryItem = (index: number) => {
|
|||||||
const toggleHistoryEdit = () => { isEditMode.value = !isEditMode.value }
|
const toggleHistoryEdit = () => { isEditMode.value = !isEditMode.value }
|
||||||
const toggleHistoryExpanded = () => { historyExpanded.value = !historyExpanded.value }
|
const toggleHistoryExpanded = () => { historyExpanded.value = !historyExpanded.value }
|
||||||
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const visibleHistory = computed(() => {
|
const visibleHistory = computed(() => {
|
||||||
if (historyExpanded.value) {
|
if (historyExpanded.value) {
|
||||||
const maxLen = searchHistory.value.length < MAX_EXPANDED_COUNT ? searchHistory.value.length : MAX_EXPANDED_COUNT
|
const maxLen = searchHistory.value.length < MAX_EXPANDED_COUNT ? searchHistory.value.length : MAX_EXPANDED_COUNT
|
||||||
@@ -577,7 +531,7 @@ const loadData = async (): Promise<void> => {
|
|||||||
const hotResult = await supabaseService.getHotProducts(30)
|
const hotResult = await supabaseService.getHotProducts(30)
|
||||||
hotProducts = hotResult as Product[]
|
hotProducts = hotResult as Product[]
|
||||||
} catch (hotError) {
|
} catch (hotError) {
|
||||||
console.error('获取热销商品失败,使用空列表:', hotError)
|
console.error('获取热销商品失败,使用空列表:', hotError)
|
||||||
hotProducts = []
|
hotProducts = []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +610,7 @@ const loadData = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const retryLoad = () => {
|
const retryLoad = () => {
|
||||||
uni.showLoading({ title: '重新加载中' })
|
uni.showLoading({ title: '重新加载中...' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
loadData()
|
loadData()
|
||||||
@@ -774,7 +728,7 @@ const performSearch = async (): Promise<void> => {
|
|||||||
image: p.main_image_url ?? '/static/default.jpg',
|
image: p.main_image_url ?? '/static/default.jpg',
|
||||||
price: p.base_price ?? 0,
|
price: p.base_price ?? 0,
|
||||||
marketPrice: formatMarketPriceText(p),
|
marketPrice: formatMarketPriceText(p),
|
||||||
specification: p.specification ?? '标准规格',
|
specification: p.specification ?? '标准规格',
|
||||||
tag: tag,
|
tag: tag,
|
||||||
sales: p.sale_count ?? 0,
|
sales: p.sale_count ?? 0,
|
||||||
salesText: formatSalesText(p),
|
salesText: formatSalesText(p),
|
||||||
@@ -848,7 +802,7 @@ const initPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('初始化失败', e)
|
console.error('初始化失败:', e)
|
||||||
isError.value = true
|
isError.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -891,16 +845,16 @@ const onSearch = () => {
|
|||||||
}
|
}
|
||||||
if (effective === '') return
|
if (effective === '') return
|
||||||
addToHistory(effective)
|
addToHistory(effective)
|
||||||
// 如果搜索词来自 placeholder,确保输入框仍为空但执行搜索
|
// 如果搜索词来自 placeholder,保持输入框为空但执行搜索
|
||||||
if (userInput === '') {
|
if (userInput === '') {
|
||||||
// 保持 searchKeyword 为空 but perform search with effective
|
// 保持 searchKeyword 为空,但使用 effective 搜索
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
}
|
}
|
||||||
// 将 searchKeyword 临时设置为 effective 以便 performSearch 使用(performSearch 使用 searchKeyword)
|
// 临时将 searchKeyword 设置为 effective,供 performSearch 使用
|
||||||
const prev = searchKeyword.value
|
const prev = searchKeyword.value
|
||||||
searchKeyword.value = effective
|
searchKeyword.value = effective
|
||||||
performSearch()
|
performSearch()
|
||||||
// 恢复输入框为空状态(如果用户未输入)
|
// 如果用户没有手动输入,则恢复为空
|
||||||
if (userInput === '') searchKeyword.value = prev
|
if (userInput === '') searchKeyword.value = prev
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -934,12 +888,12 @@ const switchSort = (type: string) => {
|
|||||||
priceSortAsc.value = !priceSortAsc.value
|
priceSortAsc.value = !priceSortAsc.value
|
||||||
} else {
|
} else {
|
||||||
activeSort.value = 'price'
|
activeSort.value = 'price'
|
||||||
priceSortAsc.value = true // 默认升序
|
priceSortAsc.value = true // 默认升序
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activeSort.value = type
|
activeSort.value = type
|
||||||
}
|
}
|
||||||
// 重新执行搜索以获取正确排序的数据
|
// 重新搜索以获取正确排序的数据
|
||||||
performSearch()
|
performSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,7 +938,7 @@ const loadMore = async (): Promise<void> => {
|
|||||||
image: p.main_image_url ?? '/static/default.jpg',
|
image: p.main_image_url ?? '/static/default.jpg',
|
||||||
price: p.base_price ?? 0,
|
price: p.base_price ?? 0,
|
||||||
marketPrice: formatMarketPriceText(p),
|
marketPrice: formatMarketPriceText(p),
|
||||||
specification: p.specification ?? '标准规格',
|
specification: p.specification ?? '标准规格',
|
||||||
tag: tag,
|
tag: tag,
|
||||||
sales: p.sale_count ?? 0,
|
sales: p.sale_count ?? 0,
|
||||||
salesText: formatSalesText(p),
|
salesText: formatSalesText(p),
|
||||||
@@ -1004,67 +958,26 @@ const loadMore = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRecommendGoods(appendSize: number): Promise<void> {
|
|
||||||
if (loadingRecommend.value) return
|
|
||||||
loadingRecommend.value = true
|
|
||||||
try {
|
|
||||||
// 如果后端支持分页接口,可在此调用;当前使用本地 pool
|
|
||||||
if (appendSize == null || appendSize <= 0) {
|
|
||||||
// 不追加时确保至少保持初始量
|
|
||||||
recommendList.value = recommendPool.value.slice(0, recommendInitialSize.value)
|
|
||||||
recommendPage.value = 1
|
|
||||||
} else {
|
|
||||||
const startIndex = recommendList.value.length
|
|
||||||
const endIndex = startIndex + appendSize
|
|
||||||
const slice = recommendPool.value.slice(startIndex, endIndex)
|
|
||||||
// 如果池不够,尝试循环或留空
|
|
||||||
if (slice.length === 0) {
|
|
||||||
// 将池随机打乱并继续追加(循环播放)
|
|
||||||
const arr = recommendPool.value.slice()
|
|
||||||
for (let i = arr.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1))
|
|
||||||
const t = arr[i]; arr[i] = arr[j]; arr[j] = t
|
|
||||||
}
|
|
||||||
recommendPool.value = arr
|
|
||||||
const more = recommendPool.value.slice(0, appendSize)
|
|
||||||
recommendList.value = recommendList.value.concat(more)
|
|
||||||
} else {
|
|
||||||
recommendList.value = recommendList.value.concat(slice)
|
|
||||||
}
|
|
||||||
recommendPage.value += 1
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('加载推荐商品失败', e)
|
|
||||||
} finally {
|
|
||||||
loadingRecommend.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshGuessList(): void {
|
|
||||||
loadRecommendGoods(recommendAppendSize.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReachBottom(): void {
|
function handleReachBottom(): void {
|
||||||
if (showResults.value === true) {
|
if (showResults.value === true) {
|
||||||
loadMore()
|
if (hasMore.value) {
|
||||||
|
loadMore()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadRecommendGoods(recommendAppendSize.value)
|
guessLoadMoreKey.value = guessLoadMoreKey.value + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMainScrollToLower(): void {
|
||||||
|
handleReachBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
onReachBottom(() => {
|
onReachBottom(() => {
|
||||||
handleReachBottom()
|
handleReachBottom()
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewProductDetail = (item: SearchResultType | GuessItemType) => {
|
|
||||||
const id = (item as GuessItemType).id
|
|
||||||
const price = (item as GuessItemType).price
|
|
||||||
const name = (item as GuessItemType).name
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewShopDetail = (shop: ShopResultType) => {
|
const viewShopDetail = (shop: ShopResultType) => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
|
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
|
||||||
@@ -1077,40 +990,40 @@ const addToCart = async (product: SearchResultType | GuessItemType) => {
|
|||||||
goToLogin('/pages/mall/consumer/search')
|
goToLogin('/pages/mall/consumer/search')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uni.showLoading({ title: '检查商品...' })
|
uni.showLoading({ title: '检查商品...' })
|
||||||
try {
|
try {
|
||||||
// 统一转换为 UTSJSONObject 访问属性
|
// 统一转换为 UTSJSONObject 访问属性
|
||||||
const prodObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
const prodObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||||||
const productId = prodObj.getString('id') ?? ''
|
const productId = prodObj.getString('id') ?? ''
|
||||||
const merchantId = prodObj.getString('merchant_id') ?? ''
|
const merchantId = prodObj.getString('merchant_id') ?? ''
|
||||||
|
|
||||||
// 检查商品是否有SKU
|
// 检查商品是否有 SKU
|
||||||
const skus = await supabaseService.getProductSkus(productId)
|
const skus = await supabaseService.getProductSkus(productId)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
if (skus.length > 0) {
|
if (skus.length > 0) {
|
||||||
// 有规格,提示并跳转到商品详情页选择规格
|
// 有规格时跳商品详情选择规格
|
||||||
uni.showToast({ title: '请选择规格', icon: 'none' })
|
uni.showToast({ title: '请选择规格', icon: 'none' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/mall/consumer/product-detail?id=' + productId
|
url: '/pages/mall/consumer/product-detail?id=' + productId
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
} else {
|
} else {
|
||||||
// 无规格,直接加入购物车
|
// 无规格则直接加入购物车
|
||||||
uni.showLoading({ title: '添加中...' })
|
uni.showLoading({ title: '添加中...' })
|
||||||
const success = await supabaseService.addToCart(productId, 1, '', merchantId)
|
const success = await supabaseService.addToCart(productId, 1, '', merchantId)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
if (success) {
|
if (success) {
|
||||||
uni.showToast({ title: '已添加到购物车', icon: 'success' })
|
uni.showToast({ title: '已添加到购物车', icon: 'success' })
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '添加失败,请先登录', icon: 'none' })
|
uni.showToast({ title: '添加失败,请先登录', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('添加到购物车异常', e)
|
console.error('添加到购物车异常', e)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({ title: '操作异常', icon: 'none' })
|
uni.showToast({ title: '操作异常', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1119,31 +1032,31 @@ const openCamera = () => {
|
|||||||
count: 1,
|
count: 1,
|
||||||
sourceType: ['camera'],
|
sourceType: ['camera'],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log('拍摄图片路径:', (res.tempFilePaths as string[])[0])
|
console.log('拍摄图片路径:', (res.tempFilePaths as string[])[0])
|
||||||
uni.showToast({ title: '已启用相机', icon: 'none' })
|
uni.showToast({ title: '已启用相机', icon: 'none' })
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('启用相机失败', err)
|
console.error('鍚敤鐩告満澶辫触', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
if (showResults.value) {
|
if (showResults.value) {
|
||||||
// 如果在搜索结果页,先返回到搜索初始页
|
// 如果在搜索结果页,先返回搜索初始页
|
||||||
showResults.value = false
|
showResults.value = false
|
||||||
searchKeyword.value = ''
|
searchKeyword.value = ''
|
||||||
} else {
|
} else {
|
||||||
// 如果在搜索初始页,则返回上一页
|
// 如果在搜索初始页,则返回上一页
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
if (pages.length > 1) {
|
if (pages.length > 1) {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
} else {
|
} else {
|
||||||
// 如果只有一页(由于深链接或重定向),返回首页
|
// 如果只有一个页面,则回首页
|
||||||
uni.switchTab({
|
uni.switchTab({
|
||||||
url: '/pages/main/index'
|
url: '/pages/main/index'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1158,7 +1071,7 @@ const goBack = () => {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 店铺搜索结果 */
|
/* 搴楅摵鎼滅储缁撴灉 */
|
||||||
.shop-results-section {
|
.shop-results-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@@ -1486,7 +1399,7 @@ const goBack = () => {
|
|||||||
margin-left: 6rpx;
|
margin-left: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 猜你需要 */
|
/* 鐚滀綘闇€瑕?*/
|
||||||
.guess-you-like {
|
.guess-you-like {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
@@ -1840,7 +1753,7 @@ const goBack = () => {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 错误状态 */
|
/* 閿欒鐘舵€?*/
|
||||||
.error-state {
|
.error-state {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1872,7 +1785,7 @@ const goBack = () => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 加载更多 */
|
/* 鍔犺浇鏇村 */
|
||||||
.loading-more {
|
.loading-more {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1932,3 +1845,5 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,39 +12,80 @@ import {
|
|||||||
HomeServiceTaskType,
|
HomeServiceTaskType,
|
||||||
HomeServiceTimelineItemType
|
HomeServiceTimelineItemType
|
||||||
} from '@/types/home-service.uts'
|
} from '@/types/home-service.uts'
|
||||||
|
import {
|
||||||
|
confirmServiceOrder,
|
||||||
|
createServiceOrder,
|
||||||
|
getServiceOrderDetail,
|
||||||
|
listConsumerServiceOrders,
|
||||||
|
rejectServiceOrderAcceptance
|
||||||
|
} from '@/services/serviceOrderService.uts'
|
||||||
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
|
import {
|
||||||
|
getServiceOrderStatusText,
|
||||||
|
type ServiceOrderStatus,
|
||||||
|
type ServiceOrderTimelineItemType,
|
||||||
|
type ServiceOrderType
|
||||||
|
} from '@/types/service-order.uts'
|
||||||
|
|
||||||
const SERVICE_CATALOG: Array<HomeServiceCatalogType> = [
|
function plainObject(source: any): any {
|
||||||
{
|
return JSON.parse(JSON.stringify(source)) as any
|
||||||
id: 'svc-001',
|
}
|
||||||
name: '基础上门护理',
|
|
||||||
category: '日常照护',
|
function readString(source: any, key: string): string {
|
||||||
price: 168,
|
const value = plainObject(source)[key]
|
||||||
durationText: '约 2 小时',
|
if (value == null) {
|
||||||
summary: '覆盖生命体征监测、基础照护、风险提醒。',
|
return ''
|
||||||
tags: ['适老化', '护理员上门', '支持家属陪同'],
|
|
||||||
suitableFor: '行动不便、术后恢复、慢病随访老人'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svc-002',
|
|
||||||
name: '康复训练指导',
|
|
||||||
category: '康复支持',
|
|
||||||
price: 260,
|
|
||||||
durationText: '约 3 小时',
|
|
||||||
summary: '提供肢体训练、步态练习和居家康复建议。',
|
|
||||||
tags: ['康复师', '步骤清晰', '可连续预约'],
|
|
||||||
suitableFor: '卒中恢复、术后康复、失能半失能老人'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'svc-003',
|
|
||||||
name: '慢病健康随访',
|
|
||||||
category: '健康管理',
|
|
||||||
price: 128,
|
|
||||||
durationText: '约 90 分钟',
|
|
||||||
summary: '完成血压血糖监测、用药核对与健康宣教。',
|
|
||||||
tags: ['随访', '慢病', '可生成记录'],
|
|
||||||
suitableFor: '高血压、糖尿病等长期管理老人'
|
|
||||||
}
|
}
|
||||||
]
|
return typeof value == 'string' ? value : String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readNumber(source: any, key: string): number {
|
||||||
|
const value = plainObject(source)[key]
|
||||||
|
if (typeof value == 'number') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
const parsed = Number(value)
|
||||||
|
return isNaN(parsed) ? 0 : parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStringArray(source: any, key: string): Array<string> {
|
||||||
|
const value = plainObject(source)[key]
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const result = [] as Array<string>
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
result.push(typeof value[i] == 'string' ? value[i] : String(value[i]))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if (typeof value == 'string' && value != '') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value) as any
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
const result = [] as Array<string>
|
||||||
|
for (let i = 0; i < parsed.length; i++) {
|
||||||
|
result.push(typeof parsed[i] == 'string' ? parsed[i] : String(parsed[i]))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [] as Array<string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [] as Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCatalogItem(source: any): HomeServiceCatalogType {
|
||||||
|
return {
|
||||||
|
id: readString(source, 'id'),
|
||||||
|
name: readString(source, 'name'),
|
||||||
|
category: readString(source, 'category'),
|
||||||
|
price: readNumber(source, 'price'),
|
||||||
|
durationText: readString(source, 'duration_text'),
|
||||||
|
summary: readString(source, 'summary'),
|
||||||
|
tags: readStringArray(source, 'tags_json'),
|
||||||
|
suitableFor: readString(source, 'suitable_for')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createTimeline(title1: string, title2: string, title3: string): Array<HomeServiceTimelineItemType> {
|
function createTimeline(title1: string, title2: string, title3: string): Array<HomeServiceTimelineItemType> {
|
||||||
return [
|
return [
|
||||||
@@ -243,31 +284,6 @@ const ADMIN_PLANS: Array<HomeServicePlanType> = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const CONSUMER_ACCEPTANCE: Array<HomeServiceAcceptanceType> = [
|
|
||||||
{
|
|
||||||
caseId: 'case-001',
|
|
||||||
caseNo: 'HS202605130001',
|
|
||||||
elderName: '李奶奶',
|
|
||||||
serviceName: '基础上门护理',
|
|
||||||
acceptanceStatus: 'waiting',
|
|
||||||
acceptanceStatusText: '待验收',
|
|
||||||
rating: 5,
|
|
||||||
feedback: '护理员按时到达,服务过程清晰,老人状态稳定。',
|
|
||||||
tags: ['准时上门', '沟通清楚', '动作规范']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
caseId: 'case-002',
|
|
||||||
caseNo: 'HS202605130002',
|
|
||||||
elderName: '张爷爷',
|
|
||||||
serviceName: '慢病健康随访',
|
|
||||||
acceptanceStatus: 'waiting',
|
|
||||||
acceptanceStatusText: '待验收',
|
|
||||||
rating: 5,
|
|
||||||
feedback: '已查看记录,等待家属最终确认。',
|
|
||||||
tags: ['记录完整', '态度耐心']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const ADMIN_RECTIFICATIONS: Array<HomeServiceRectificationType> = [
|
const ADMIN_RECTIFICATIONS: Array<HomeServiceRectificationType> = [
|
||||||
{
|
{
|
||||||
caseId: 'case-001',
|
caseId: 'case-001',
|
||||||
@@ -355,13 +371,6 @@ function clonePlan(item: HomeServicePlanType): HomeServicePlanType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneAcceptance(item: HomeServiceAcceptanceType): HomeServiceAcceptanceType {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
tags: item.tags.slice(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneRectification(item: HomeServiceRectificationType): HomeServiceRectificationType {
|
function cloneRectification(item: HomeServiceRectificationType): HomeServiceRectificationType {
|
||||||
return {
|
return {
|
||||||
...item
|
...item
|
||||||
@@ -375,68 +384,196 @@ function cloneSettlement(item: HomeServiceSettlementType): HomeServiceSettlement
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchHomeServiceCatalog(): Promise<Array<HomeServiceCatalogType>> {
|
export async function fetchHomeServiceCatalog(): Promise<Array<HomeServiceCatalogType>> {
|
||||||
await delay()
|
const response = await supa
|
||||||
return SERVICE_CATALOG.map((item) => ({ ...item, tags: item.tags.slice(0) }))
|
.from('hss_service_catalog')
|
||||||
|
.select('id, name, category, price, duration_text, summary, tags_json, suitable_for, sort_no')
|
||||||
|
.eq('status', 1)
|
||||||
|
.is('deleted_at', null)
|
||||||
|
.order('sort_no', { ascending: true })
|
||||||
|
.execute()
|
||||||
|
if (response.error != null || response.data == null || !Array.isArray(response.data)) {
|
||||||
|
return [] as Array<HomeServiceCatalogType>
|
||||||
|
}
|
||||||
|
const result = [] as Array<HomeServiceCatalogType>
|
||||||
|
for (let i = 0; i < response.data.length; i++) {
|
||||||
|
result.push(parseCatalogItem(response.data[i]))
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchConsumerHomeServiceCases(): Promise<Array<HomeServiceCaseType>> {
|
export async function fetchConsumerHomeServiceCases(): Promise<Array<HomeServiceCaseType>> {
|
||||||
await delay()
|
const orders = await listConsumerServiceOrders()
|
||||||
return CASE_STORE.map((item) => cloneCase(item))
|
const result = [] as Array<HomeServiceCaseType>
|
||||||
|
for (let i = 0; i < orders.length; i++) {
|
||||||
|
result.push(mapOrderToCase(orders[i]))
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchConsumerHomeServiceCaseDetail(caseId: string): Promise<HomeServiceCaseType | null> {
|
export async function fetchConsumerHomeServiceCaseDetail(caseId: string): Promise<HomeServiceCaseType | null> {
|
||||||
await delay()
|
const detail = await getServiceOrderDetail(caseId)
|
||||||
const target = CASE_STORE.find((item) => item.id == caseId)
|
if (detail != null) {
|
||||||
return target == null ? null : cloneCase(target)
|
return mapOrderToCase(detail)
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createHomeServiceApplication(draft: HomeServiceApplicationDraftType): Promise<HomeServiceCaseType> {
|
export async function createHomeServiceApplication(draft: HomeServiceApplicationDraftType): Promise<HomeServiceCaseType | null> {
|
||||||
await delay()
|
const catalog = await fetchHomeServiceCatalog()
|
||||||
const matchedService = SERVICE_CATALOG.find((item) => item.id == draft.serviceId)
|
let matchedService: HomeServiceCatalogType | null = null
|
||||||
const created: HomeServiceCaseType = {
|
for (let i = 0; i < catalog.length; i++) {
|
||||||
id: 'case-' + String(CASE_STORE.length + 1).padStart(3, '0'),
|
if (catalog[i].id == draft.serviceId) {
|
||||||
caseNo: 'HS20260513' + String(CASE_STORE.length + 1).padStart(4, '0'),
|
matchedService = catalog[i]
|
||||||
status: 'submitted',
|
break
|
||||||
statusText: '已提交',
|
}
|
||||||
statusTone: 'primary',
|
}
|
||||||
serviceName: draft.serviceName,
|
if (matchedService != null && draft.serviceAddressSnapshot != null) {
|
||||||
serviceTime: draft.preferredTime,
|
const createdOrder = await createServiceOrder({
|
||||||
applicantName: draft.applicantName,
|
service: matchedService,
|
||||||
elderName: draft.elderName,
|
address: {
|
||||||
age: draft.age,
|
addressId: draft.serviceAddressSnapshot.addressId,
|
||||||
phone: draft.phone,
|
contactName: draft.serviceAddressSnapshot.contactName,
|
||||||
address: draft.address,
|
contactPhone: draft.serviceAddressSnapshot.contactPhone,
|
||||||
summary: draft.demandSummary,
|
province: '',
|
||||||
currentStep: 1,
|
city: '',
|
||||||
totalSteps: 8,
|
district: '',
|
||||||
staffName: '待分配',
|
detailAddress: draft.serviceAddressSnapshot.addressDetail,
|
||||||
staffPhone: '待分配',
|
fullAddress: draft.serviceAddressSnapshot.fullAddress,
|
||||||
amount: matchedService != null ? matchedService.price : 0,
|
latitude: draft.serviceAddressSnapshot.latitude,
|
||||||
timeline: [
|
longitude: draft.serviceAddressSnapshot.longitude,
|
||||||
{
|
coordinateType: draft.serviceAddressSnapshot.coordinateType,
|
||||||
id: 'new-1',
|
remark: draft.serviceAddressSnapshot.remark
|
||||||
title: '已提交申请',
|
},
|
||||||
time: '2026-05-13 10:00',
|
recipientName: draft.elderName,
|
||||||
description: '系统已接收申请,等待受理。'
|
recipientPhone: draft.phone,
|
||||||
}
|
contactName: draft.applicantName,
|
||||||
]
|
contactPhone: draft.phone,
|
||||||
|
appointmentTime: draft.preferredTime,
|
||||||
|
remark: draft.demandSummary
|
||||||
|
})
|
||||||
|
if (createdOrder != null) {
|
||||||
|
return mapOrderToCase(createdOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaseStep(status: ServiceOrderStatus): number {
|
||||||
|
if (status == 'created') return 1
|
||||||
|
if (status == 'assigned') return 3
|
||||||
|
if (status == 'accepted') return 4
|
||||||
|
if (status == 'departed') return 5
|
||||||
|
if (status == 'arrived' || status == 'in_service') return 6
|
||||||
|
if (status == 'pending_acceptance') return 7
|
||||||
|
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 8
|
||||||
|
if (status == 'rejected' || status == 'cancelled' || status == 'exception') return 7
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaseTone(status: ServiceOrderStatus): string {
|
||||||
|
if (status == 'created' || status == 'assigned' || status == 'pending_acceptance') return 'warning'
|
||||||
|
if (status == 'accepted' || status == 'departed' || status == 'arrived' || status == 'in_service') return 'primary'
|
||||||
|
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 'success'
|
||||||
|
if (status == 'exception' || status == 'cancelled' || status == 'rejected') return 'danger'
|
||||||
|
return 'neutral'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimelineDescription(log: ServiceOrderTimelineItemType): string {
|
||||||
|
if (log.remark != '') {
|
||||||
|
return log.remark
|
||||||
|
}
|
||||||
|
if (log.toStatus == 'created') return '平台已接收预约申请,等待后续处理。'
|
||||||
|
if (log.toStatus == 'assigned') return '系统已完成派单,正在通知服务人员。'
|
||||||
|
if (log.toStatus == 'accepted') return '服务人员已接单,正在准备上门。'
|
||||||
|
if (log.toStatus == 'departed') return '服务人员已出发,正在前往服务地点。'
|
||||||
|
if (log.toStatus == 'arrived') return '服务人员已到达服务地点。'
|
||||||
|
if (log.toStatus == 'in_service') return '服务已开始执行,请留意后续进度。'
|
||||||
|
if (log.toStatus == 'completed') return '服务执行已完成,正在整理结果。'
|
||||||
|
if (log.toStatus == 'pending_acceptance') return '服务记录已提交,等待家属验收。'
|
||||||
|
if (log.toStatus == 'accepted_by_user') return '家属已确认本次服务结果。'
|
||||||
|
if (log.toStatus == 'reviewed') return '家属已完成评价反馈。'
|
||||||
|
if (log.toStatus == 'settled') return '本次服务已完成结算归档。'
|
||||||
|
if (log.toStatus == 'cancelled') return '服务单已取消。'
|
||||||
|
if (log.toStatus == 'rejected') return '服务人员未接受该工单。'
|
||||||
|
return '服务过程状态已更新。'
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatServiceAppointmentText(value: string): string {
|
||||||
|
if (value == '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (value.indexOf('上午') >= 0 || value.indexOf('下午') >= 0 || value.indexOf('晚上') >= 0) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
const parsed = Date.parse(value)
|
||||||
|
if (!isNaN(parsed)) {
|
||||||
|
const date = new Date(parsed)
|
||||||
|
let year = date.getFullYear()
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
if (year < currentYear - 1) {
|
||||||
|
year = currentYear
|
||||||
|
}
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute
|
||||||
|
}
|
||||||
|
const monthDayMatch = value.match(/(\d{2})\/(\d{2})/)
|
||||||
|
const timeMatch = value.match(/(\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?)/)
|
||||||
|
if (monthDayMatch != null) {
|
||||||
|
const month = monthDayMatch[1] ?? ''
|
||||||
|
const day = monthDayMatch[2] ?? ''
|
||||||
|
const timeText = timeMatch != null ? (timeMatch[1] ?? '') : ''
|
||||||
|
if (month != '' && day != '') {
|
||||||
|
return String(new Date().getFullYear()) + '-' + month + '-' + day + (timeText != '' ? ' ' + timeText.replace(/\s+/g, '') : '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value.replace('T', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapLogsToTimeline(logs: Array<ServiceOrderTimelineItemType>): Array<HomeServiceTimelineItemType> {
|
||||||
|
const timeline = [] as Array<HomeServiceTimelineItemType>
|
||||||
|
for (let i = 0; i < logs.length; i++) {
|
||||||
|
timeline.push({
|
||||||
|
id: logs[i].id,
|
||||||
|
title: getServiceOrderStatusText(logs[i].toStatus),
|
||||||
|
time: logs[i].createdAt,
|
||||||
|
description: getTimelineDescription(logs[i])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
|
||||||
|
return {
|
||||||
|
id: order.id,
|
||||||
|
caseNo: order.orderNo,
|
||||||
|
status: order.status,
|
||||||
|
statusText: getServiceOrderStatusText(order.status),
|
||||||
|
statusTone: getCaseTone(order.status),
|
||||||
|
serviceName: order.serviceName,
|
||||||
|
serviceTime: formatServiceAppointmentText(order.appointmentTime),
|
||||||
|
applicantName: order.contactName,
|
||||||
|
elderName: order.recipientName,
|
||||||
|
age: 0,
|
||||||
|
phone: order.contactPhone,
|
||||||
|
address: order.addressSnapshot.fullAddress,
|
||||||
|
summary: order.remark,
|
||||||
|
currentStep: getCaseStep(order.status),
|
||||||
|
totalSteps: 8,
|
||||||
|
staffName: order.staffName == '' ? '待分配' : order.staffName,
|
||||||
|
staffPhone: order.staffPhone == '' ? '待分配' : order.staffPhone,
|
||||||
|
amount: order.serviceSnapshot.price,
|
||||||
|
checkinTime: order.executionRecord != null ? order.executionRecord.checkinTime : '',
|
||||||
|
checkinAddress: order.executionRecord != null ? order.executionRecord.checkinAddress : '',
|
||||||
|
serviceStartedAt: order.executionRecord != null ? order.executionRecord.serviceStartedAt : order.serviceStartedAt,
|
||||||
|
serviceFinishedAt: order.executionRecord != null ? order.executionRecord.serviceFinishedAt : order.completedAt,
|
||||||
|
executionSummary: order.executionRecord != null ? (order.executionRecord.summary != '' ? order.executionRecord.summary : order.executionRecord.remark) : '',
|
||||||
|
evidenceCount: order.evidenceFiles.length,
|
||||||
|
serviceAddressSnapshot: null,
|
||||||
|
timeline: mapLogsToTimeline(order.logs)
|
||||||
}
|
}
|
||||||
CASE_STORE.unshift(created)
|
|
||||||
ADMIN_APPLICATIONS.unshift({
|
|
||||||
id: 'admin-app-' + String(ADMIN_APPLICATIONS.length + 1).padStart(3, '0'),
|
|
||||||
caseId: created.id,
|
|
||||||
caseNo: created.caseNo,
|
|
||||||
status: 'submitted',
|
|
||||||
statusText: '已提交',
|
|
||||||
statusTone: 'primary',
|
|
||||||
elderName: created.elderName,
|
|
||||||
serviceName: created.serviceName,
|
|
||||||
preferredTime: created.serviceTime,
|
|
||||||
assessmentResult: '待受理',
|
|
||||||
dispatcherName: '待分配',
|
|
||||||
staffName: '待分配'
|
|
||||||
})
|
|
||||||
return cloneCase(created)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchWorkerTasks(): Promise<Array<HomeServiceTaskType>> {
|
export async function fetchWorkerTasks(): Promise<Array<HomeServiceTaskType>> {
|
||||||
@@ -738,9 +875,21 @@ export async function submitAdminServicePlan(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchConsumerAcceptanceDetail(caseId: string): Promise<HomeServiceAcceptanceType | null> {
|
export async function fetchConsumerAcceptanceDetail(caseId: string): Promise<HomeServiceAcceptanceType | null> {
|
||||||
await delay()
|
const detail = await getServiceOrderDetail(caseId)
|
||||||
const target = CONSUMER_ACCEPTANCE.find((item) => item.caseId == caseId)
|
if (detail != null) {
|
||||||
return target == null ? null : cloneAcceptance(target)
|
return {
|
||||||
|
caseId: detail.id,
|
||||||
|
caseNo: detail.orderNo,
|
||||||
|
elderName: detail.recipientName,
|
||||||
|
serviceName: detail.serviceName,
|
||||||
|
acceptanceStatus: detail.status,
|
||||||
|
acceptanceStatusText: getServiceOrderStatusText(detail.status),
|
||||||
|
rating: detail.review != null ? detail.review.rating : 5,
|
||||||
|
feedback: detail.review != null ? detail.review.content : '',
|
||||||
|
tags: detail.review != null ? detail.review.tags : [] as Array<string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function submitConsumerAcceptance(
|
export async function submitConsumerAcceptance(
|
||||||
@@ -750,40 +899,18 @@ export async function submitConsumerAcceptance(
|
|||||||
feedback: string,
|
feedback: string,
|
||||||
tags: Array<string>
|
tags: Array<string>
|
||||||
): Promise<HomeServiceAcceptanceType | null> {
|
): Promise<HomeServiceAcceptanceType | null> {
|
||||||
await delay()
|
if (approved) {
|
||||||
const target = CONSUMER_ACCEPTANCE.find((item) => item.caseId == caseId)
|
const result = await confirmServiceOrder(caseId, rating, feedback, tags)
|
||||||
if (target == null) {
|
if (result != null) {
|
||||||
|
return await fetchConsumerAcceptanceDetail(caseId)
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const rejected = await rejectServiceOrderAcceptance(caseId, feedback)
|
||||||
target.acceptanceStatus = approved ? 'approved' : 'rejected'
|
if (rejected != null) {
|
||||||
target.acceptanceStatusText = approved ? '已验收' : '已退回'
|
return await fetchConsumerAcceptanceDetail(caseId)
|
||||||
target.rating = rating
|
|
||||||
target.feedback = feedback
|
|
||||||
target.tags = tags.slice(0)
|
|
||||||
|
|
||||||
const relatedCase = CASE_STORE.find((item) => item.id == caseId)
|
|
||||||
if (relatedCase != null) {
|
|
||||||
relatedCase.status = approved ? 'completed' : 'revision_required'
|
|
||||||
relatedCase.statusText = approved ? '已完成' : '待整改'
|
|
||||||
relatedCase.statusTone = approved ? 'success' : 'warning'
|
|
||||||
relatedCase.currentStep = approved ? 8 : 7
|
|
||||||
relatedCase.timeline.unshift({
|
|
||||||
id: 'case-accept-' + String(relatedCase.timeline.length + 1),
|
|
||||||
title: approved ? '家属已验收' : '家属退回整改',
|
|
||||||
time: '2026-05-13 17:10',
|
|
||||||
description: feedback
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
const relatedAdmin = ADMIN_APPLICATIONS.find((item) => item.caseId == caseId)
|
|
||||||
if (relatedAdmin != null) {
|
|
||||||
relatedAdmin.status = approved ? 'completed' : 'revision_required'
|
|
||||||
relatedAdmin.statusText = approved ? '已完成' : '待整改'
|
|
||||||
relatedAdmin.statusTone = approved ? 'success' : 'warning'
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneAcceptance(target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAdminRectificationDetail(caseId: string): Promise<HomeServiceRectificationType | null> {
|
export async function fetchAdminRectificationDetail(caseId: string): Promise<HomeServiceRectificationType | null> {
|
||||||
|
|||||||
@@ -1,124 +1,135 @@
|
|||||||
import supa from '@/components/supadb/aksupainstance.uts'
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
import { getCurrentUserId } from '@/utils/store.uts'
|
import { getCurrentUserId } from '@/utils/store.uts'
|
||||||
import { getDeliveryProfileByUserId } from '@/api/delivery.uts'
|
import { getCurrentUser } from '@/utils/store.uts'
|
||||||
import { getServiceOrderStatusText, normalizeServiceOrderStatus, type ServiceOrderStatus } from '@/types/service-order.uts'
|
import type { UserAddress } from '@/utils/supabaseService.uts'
|
||||||
import type {
|
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||||
DeliveryCheckinPayloadType,
|
import {
|
||||||
DeliveryDashboardType,
|
getServiceOrderStatusText,
|
||||||
DeliveryLocationType,
|
normalizeServiceOrderStatus,
|
||||||
DeliveryOrderType,
|
type ServiceOrderAddressSnapshotType,
|
||||||
DeliveryServiceRecordType
|
type ServiceEvidenceFileType,
|
||||||
} from '@/types/delivery.uts'
|
type ServiceExecutionRecordType,
|
||||||
|
type ServiceOrderStatus,
|
||||||
|
type ServiceOrderTimelineItemType,
|
||||||
|
type ServiceOrderType,
|
||||||
|
type ServiceReviewType
|
||||||
|
} from '@/types/service-order.uts'
|
||||||
|
|
||||||
function nowIso(): string {
|
export type CreateServiceOrderParams = {
|
||||||
return new Date().toISOString()
|
service: HomeServiceCatalogType
|
||||||
}
|
address: ServiceOrderAddressSnapshotType
|
||||||
|
recipientName: string
|
||||||
|
recipientPhone: string
|
||||||
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
appointmentTime: string
|
||||||
|
remark: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function nowText(): string {
|
||||||
|
return new Date().toISOString().replace('T', ' ').substring(0, 19)
|
||||||
|
}
|
||||||
|
|
||||||
function buildId(prefix: string): string {
|
function buildId(prefix: string): string {
|
||||||
return prefix + '-' + String(Date.now()) + '-' + String(Math.floor(Math.random() * 100000)).padStart(5, '0')
|
return prefix + '-' + String(Date.now()) + '-' + String(Math.floor(Math.random() * 100000)).padStart(5, '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCurrentStaffId(): Promise<string> {
|
function buildOrderNo(): string {
|
||||||
const userId = getCurrentUserId()
|
const date = new Date()
|
||||||
if (userId == '') {
|
const y = String(date.getFullYear())
|
||||||
return ''
|
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
}
|
const d = String(date.getDate()).padStart(2, '0')
|
||||||
const profile = await getDeliveryProfileByUserId(userId)
|
const h = String(date.getHours()).padStart(2, '0')
|
||||||
return profile != null ? profile.id : ''
|
const i = String(date.getMinutes()).padStart(2, '0')
|
||||||
}
|
const s = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
return 'HS' + y + m + d + h + i + s + String(Math.floor(Math.random() * 900) + 100)
|
||||||
|
}
|
||||||
|
|
||||||
async function insertStatusLog(orderId: string, fromStatus: string, toStatus: ServiceOrderStatus, remark: string): Promise<void> {
|
function isUuidLike(value: string): boolean {
|
||||||
const userId = getCurrentUserId()
|
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test(value)
|
||||||
await supa.from('hss_service_order_status_logs').insert({
|
}
|
||||||
id: buildId('slog'),
|
|
||||||
order_id: orderId,
|
|
||||||
from_status: fromStatus,
|
|
||||||
to_status: toStatus,
|
|
||||||
operator_id: userId == '' ? null : userId,
|
|
||||||
operator_role: 'delivery',
|
|
||||||
remark,
|
|
||||||
created_at: nowIso()
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyOrder(): DeliveryOrderType {
|
function normalizeUuidOrNull(value: string): string | null {
|
||||||
return {
|
if (value == '') {
|
||||||
id: '',
|
return null
|
||||||
orderNo: '',
|
|
||||||
serviceType: '',
|
|
||||||
serviceName: '',
|
|
||||||
serviceCategory: '',
|
|
||||||
serviceItems: [] as Array<any>,
|
|
||||||
elderId: '',
|
|
||||||
elderName: '',
|
|
||||||
elderNameMasked: '',
|
|
||||||
elderGender: '',
|
|
||||||
elderAge: 0,
|
|
||||||
elderPhone: '',
|
|
||||||
elderPhoneMasked: '',
|
|
||||||
fullElderName: '',
|
|
||||||
fullPhone: '',
|
|
||||||
contactRelation: '家属',
|
|
||||||
addressSummary: '',
|
|
||||||
address: '',
|
|
||||||
addressDetail: '',
|
|
||||||
fullAddress: '',
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
appointmentTime: '',
|
|
||||||
appointmentStartTime: '',
|
|
||||||
appointmentEndTime: '',
|
|
||||||
duration: 90,
|
|
||||||
estimatedDuration: 90,
|
|
||||||
price: 0,
|
|
||||||
staffIncome: 0,
|
|
||||||
distance: '',
|
|
||||||
actualStartTime: '',
|
|
||||||
actualEndTime: '',
|
|
||||||
status: 'pending_assignment' as any,
|
|
||||||
statusText: '',
|
|
||||||
statusTone: 'warning',
|
|
||||||
riskTags: [] as Array<string>,
|
|
||||||
healthTags: [] as Array<string>,
|
|
||||||
careLevel: '',
|
|
||||||
needFamilyPresent: false,
|
|
||||||
needMaterials: false,
|
|
||||||
remark: '',
|
|
||||||
merchantId: '',
|
|
||||||
merchantName: '',
|
|
||||||
deliveryStaffId: '',
|
|
||||||
deliveryStaffName: '',
|
|
||||||
acceptTime: '',
|
|
||||||
departTime: '',
|
|
||||||
arriveTime: '',
|
|
||||||
checkinTime: '',
|
|
||||||
finishTime: '',
|
|
||||||
cancelReason: '',
|
|
||||||
exceptionType: '',
|
|
||||||
exceptionDesc: '',
|
|
||||||
evidenceList: [] as Array<any>,
|
|
||||||
signatureUrl: '',
|
|
||||||
signatureName: '',
|
|
||||||
satisfactionStatus: '',
|
|
||||||
settlementStatus: '',
|
|
||||||
archiveStatus: '',
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
contactName: '',
|
|
||||||
contactPhone: '',
|
|
||||||
notices: [] as Array<string>,
|
|
||||||
timeline: [] as Array<any>,
|
|
||||||
statusLog: [] as Array<any>,
|
|
||||||
serviceSummary: '',
|
|
||||||
progressNote: '',
|
|
||||||
distanceKm: '',
|
|
||||||
allowCheckinRadiusMeters: 100,
|
|
||||||
lastLocation: null,
|
|
||||||
trackPoints: [] as Array<any>,
|
|
||||||
serviceRecord: null,
|
|
||||||
abnormalReport: null
|
|
||||||
} as DeliveryOrderType
|
|
||||||
}
|
}
|
||||||
|
return isUuidLike(value) ? value : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAppointmentTime(value: string): string | null {
|
||||||
|
const text = value.trim()
|
||||||
|
if (text == '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?$/.test(text)) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}(\s+(上午|下午|晚上))$/.test(text)) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}T/.test(text)) {
|
||||||
|
const parsed = Date.parse(text)
|
||||||
|
if (!isNaN(parsed)) {
|
||||||
|
const date = new Date(parsed)
|
||||||
|
const year = String(date.getFullYear())
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute
|
||||||
|
}
|
||||||
|
return text.replace('T', ' ')
|
||||||
|
}
|
||||||
|
const monthDayMatch = text.match(/(\d{2})\/(\d{2})/)
|
||||||
|
if (monthDayMatch != null) {
|
||||||
|
const month = monthDayMatch[1] ?? ''
|
||||||
|
const day = monthDayMatch[2] ?? ''
|
||||||
|
if (month == '' || day == '') {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const year = String(new Date().getFullYear())
|
||||||
|
const rangeMatch = text.match(/(\d{2}:\d{2})(\s*-\s*\d{2}:\d{2})?/)
|
||||||
|
if (rangeMatch != null) {
|
||||||
|
const startTime = rangeMatch[1] ?? ''
|
||||||
|
const endRange = rangeMatch[2] ?? ''
|
||||||
|
if (startTime != '') {
|
||||||
|
return year + '-' + month + '-' + day + ' ' + startTime + endRange.replace(/\s+/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tailText = text.substring(text.indexOf(month + '/' + day) + 5).trim()
|
||||||
|
return year + '-' + month + '-' + day + (tailText != '' ? ' ' + tailText : '')
|
||||||
|
}
|
||||||
|
const explicitParsed = Date.parse(text)
|
||||||
|
if (!isNaN(explicitParsed) && /^\d{4}\//.test(text)) {
|
||||||
|
const date = new Date(explicitParsed)
|
||||||
|
const year = String(date.getFullYear())
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute
|
||||||
|
}
|
||||||
|
const year = String(new Date().getFullYear())
|
||||||
|
return /^\d{2}-\d{2}\s+\d{2}:\d{2}/.test(text) ? year + '-' + text : text
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeParseObject(value: string): UTSJSONObject {
|
||||||
|
if (value == '') {
|
||||||
|
return JSON.parse('{}') as UTSJSONObject
|
||||||
|
}
|
||||||
|
return JSON.parse(value) as UTSJSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeParseArray(value: string): Array<string> {
|
||||||
|
if (value == '') {
|
||||||
|
return [] as Array<string>
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(value) as any
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
return parsed as Array<string>
|
||||||
|
}
|
||||||
|
return [] as Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
function safeJsonField(source: any, key: string): string {
|
function safeJsonField(source: any, key: string): string {
|
||||||
const plain = JSON.parse(JSON.stringify(source)) as any
|
const plain = JSON.parse(JSON.stringify(source)) as any
|
||||||
@@ -127,374 +138,387 @@ function safeJsonField(source: any, key: string): string {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return JSON.stringify(value)
|
return JSON.stringify(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusToDeliveryStatus(status: ServiceOrderStatus): string {
|
function plainObject(source: any): any {
|
||||||
if (status == 'assigned') return 'pending_assignment'
|
return JSON.parse(JSON.stringify(source)) as any
|
||||||
if (status == 'accepted') return 'accepted'
|
}
|
||||||
if (status == 'departed') return 'departed'
|
|
||||||
if (status == 'arrived') return 'arrived'
|
|
||||||
if (status == 'in_service') return 'in_service'
|
|
||||||
if (status == 'pending_acceptance') return 'pending_acceptance'
|
|
||||||
if (status == 'reviewed' || status == 'accepted_by_user' || status == 'settled') return 'completed'
|
|
||||||
if (status == 'rejected') return 'rejected'
|
|
||||||
if (status == 'cancelled') return 'cancelled'
|
|
||||||
if (status == 'exception') return 'abnormal'
|
|
||||||
return 'pending_assignment'
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusTone(status: ServiceOrderStatus): string {
|
function readString(source: any, key: string): string {
|
||||||
if (status == 'pending_acceptance' || status == 'assigned') return 'warning'
|
const value = plainObject(source)[key]
|
||||||
if (status == 'accepted' || status == 'departed' || status == 'arrived' || status == 'in_service') return 'primary'
|
if (value == null) {
|
||||||
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 'success'
|
return ''
|
||||||
if (status == 'rejected' || status == 'cancelled' || status == 'exception') return 'danger'
|
|
||||||
return 'warning'
|
|
||||||
}
|
}
|
||||||
|
return typeof value == 'string' ? value : String(value)
|
||||||
|
}
|
||||||
|
|
||||||
async function parseDeliveryOrder(orderId: string, item: any): Promise<DeliveryOrderType> {
|
function readNumber(source: any, key: string): number {
|
||||||
const order = emptyOrder()
|
const value = plainObject(source)[key]
|
||||||
const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
if (typeof value == 'number') {
|
||||||
const addressRaw = safeJsonField(item, 'address_snapshot_json')
|
return value
|
||||||
const serviceRaw = safeJsonField(item, 'service_snapshot_json')
|
|
||||||
const addressObj = JSON.parse(addressRaw == '' ? '{}' : addressRaw) as UTSJSONObject
|
|
||||||
const serviceObj = JSON.parse(serviceRaw == '' ? '{}' : serviceRaw) as UTSJSONObject
|
|
||||||
const normalizedStatus = normalizeServiceOrderStatus(obj.getString('status') ?? '')
|
|
||||||
order.id = obj.getString('id') ?? ''
|
|
||||||
order.orderNo = obj.getString('order_no') ?? ''
|
|
||||||
order.serviceType = serviceObj.getString('category') ?? '居家服务'
|
|
||||||
order.serviceName = obj.getString('service_name') ?? ''
|
|
||||||
order.serviceCategory = serviceObj.getString('category') ?? ''
|
|
||||||
order.elderName = obj.getString('recipient_name') ?? ''
|
|
||||||
order.elderNameMasked = order.elderName
|
|
||||||
order.fullElderName = order.elderName
|
|
||||||
order.elderPhone = obj.getString('recipient_phone') ?? ''
|
|
||||||
order.elderPhoneMasked = order.elderPhone
|
|
||||||
order.fullPhone = order.elderPhone
|
|
||||||
order.contactName = obj.getString('contact_name') ?? ''
|
|
||||||
order.contactPhone = obj.getString('contact_phone') ?? ''
|
|
||||||
order.contactRelation = '家属'
|
|
||||||
order.address = addressObj.getString('fullAddress') ?? ''
|
|
||||||
order.addressDetail = addressObj.getString('detailAddress') ?? ''
|
|
||||||
order.fullAddress = order.address
|
|
||||||
order.latitude = addressObj.getNumber('latitude') ?? 0
|
|
||||||
order.longitude = addressObj.getNumber('longitude') ?? 0
|
|
||||||
order.appointmentTime = obj.getString('appointment_time') ?? ''
|
|
||||||
order.appointmentStartTime = order.appointmentTime
|
|
||||||
order.appointmentEndTime = order.appointmentTime
|
|
||||||
order.duration = 90
|
|
||||||
order.estimatedDuration = 90
|
|
||||||
order.price = serviceObj.getNumber('price') ?? 0
|
|
||||||
order.staffIncome = order.price
|
|
||||||
order.status = statusToDeliveryStatus(normalizedStatus) as any
|
|
||||||
order.statusText = getServiceOrderStatusText(normalizedStatus)
|
|
||||||
order.statusTone = statusTone(normalizedStatus)
|
|
||||||
order.remark = obj.getString('remark') ?? ''
|
|
||||||
order.deliveryStaffId = obj.getString('current_staff_id') ?? ''
|
|
||||||
order.acceptTime = obj.getString('accepted_at') ?? ''
|
|
||||||
order.departTime = obj.getString('departed_at') ?? ''
|
|
||||||
order.arriveTime = obj.getString('arrived_at') ?? ''
|
|
||||||
order.actualStartTime = obj.getString('service_started_at') ?? ''
|
|
||||||
order.startServiceTime = order.actualStartTime
|
|
||||||
order.finishTime = obj.getString('completed_at') ?? ''
|
|
||||||
order.createdAt = obj.getString('created_at') ?? ''
|
|
||||||
order.updatedAt = obj.getString('updated_at') ?? ''
|
|
||||||
order.allowCheckinRadiusMeters = 100
|
|
||||||
const logsResponse = await supa.from('hss_service_order_status_logs').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (logsResponse.data != null) {
|
|
||||||
const rawLogs = logsResponse.data as any[]
|
|
||||||
for (let i = 0; i < rawLogs.length; i++) {
|
|
||||||
const logObj = JSON.parse(JSON.stringify(rawLogs[i])) as UTSJSONObject
|
|
||||||
order.timeline.push({
|
|
||||||
id: logObj.getString('id') ?? '',
|
|
||||||
title: getServiceOrderStatusText(normalizeServiceOrderStatus(logObj.getString('to_status') ?? 'created')),
|
|
||||||
time: logObj.getString('created_at') ?? '',
|
|
||||||
description: logObj.getString('remark') ?? ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
if (typeof value == 'string' && value != '') {
|
||||||
if (recordResponse.data != null) {
|
const parsed = Number(value)
|
||||||
const raw = recordResponse.data as any[]
|
return isNaN(parsed) ? 0 : parsed
|
||||||
if (raw.length > 0) {
|
|
||||||
const recordObj = JSON.parse(JSON.stringify(raw[0])) as UTSJSONObject
|
|
||||||
order.checkinTime = recordObj.getString('checkin_time') ?? ''
|
|
||||||
order.serviceRecord = {
|
|
||||||
id: recordObj.getString('id') ?? '',
|
|
||||||
orderId: orderId,
|
|
||||||
startTime: recordObj.getString('service_started_at') ?? '',
|
|
||||||
endTime: recordObj.getString('service_finished_at') ?? '',
|
|
||||||
actualDurationMinutes: recordObj.getNumber('actual_duration_minutes') ?? 0,
|
|
||||||
serviceItems: [] as Array<any>,
|
|
||||||
serviceContent: [] as Array<string>,
|
|
||||||
processNote: recordObj.getString('summary') ?? '',
|
|
||||||
elderStatus: '',
|
|
||||||
healthMetrics: { bloodPressure: '', heartRate: '', bloodSugar: '', bloodOxygen: '' },
|
|
||||||
materialsUsed: '',
|
|
||||||
abnormalNote: '',
|
|
||||||
photos: [] as Array<string>,
|
|
||||||
staffRemark: recordObj.getString('remark') ?? '',
|
|
||||||
familyConfirmation: { method: 'none', code: '', signatureName: '', signatureUrl: '', confirmedAt: '' },
|
|
||||||
createdAt: recordObj.getString('created_at') ?? '',
|
|
||||||
updatedAt: recordObj.getString('updated_at') ?? ''
|
|
||||||
} as any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const evidenceResponse = await supa.from('hss_service_evidence_files').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (evidenceResponse.data != null) {
|
|
||||||
const rawEvidence = evidenceResponse.data as any[]
|
|
||||||
for (let i = 0; i < rawEvidence.length; i++) {
|
|
||||||
const evidenceObj = JSON.parse(JSON.stringify(rawEvidence[i])) as UTSJSONObject
|
|
||||||
order.evidenceList.push({
|
|
||||||
id: evidenceObj.getString('id') ?? '',
|
|
||||||
orderId: orderId,
|
|
||||||
phase: evidenceObj.getString('phase') ?? '',
|
|
||||||
fileType: evidenceObj.getString('file_type') ?? 'image',
|
|
||||||
name: evidenceObj.getString('storage_path') ?? '',
|
|
||||||
url: evidenceObj.getString('file_url') ?? '',
|
|
||||||
localPath: '',
|
|
||||||
status: 'success',
|
|
||||||
progress: 100,
|
|
||||||
retryable: false,
|
|
||||||
createdAt: evidenceObj.getString('created_at') ?? ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return order
|
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
export async function getDashboard(): Promise<DeliveryDashboardType> {
|
function parseTimeline(item: any): ServiceOrderTimelineItemType {
|
||||||
const orders = await getOrdersByTab('all')
|
|
||||||
let pending = 0
|
|
||||||
let today = 0
|
|
||||||
let serving = 0
|
|
||||||
let completed = 0
|
|
||||||
let nextOrder: DeliveryOrderType | null = null
|
|
||||||
for (let i = 0; i < orders.length; i++) {
|
|
||||||
const item = orders[i]
|
|
||||||
if (item.status == 'pending_assignment') pending++
|
|
||||||
if (item.status == 'pending_assignment' || item.status == 'accepted' || item.status == 'departed' || item.status == 'arrived' || item.status == 'in_service') today++
|
|
||||||
if (item.status == 'in_service') serving++
|
|
||||||
if (item.status == 'completed' || item.status == 'pending_acceptance') completed++
|
|
||||||
if (nextOrder == null && item.status != 'completed' && item.status != 'cancelled' && item.status != 'abnormal') {
|
|
||||||
nextOrder = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
pendingAssignmentCount: pending,
|
id: readString(item, 'id'),
|
||||||
pendingAcceptCount: pending,
|
orderId: readString(item, 'order_id'),
|
||||||
todayOrderCount: today,
|
fromStatus: readString(item, 'from_status'),
|
||||||
pendingDepartCount: 0,
|
toStatus: normalizeServiceOrderStatus(readString(item, 'to_status')),
|
||||||
servingCount: serving,
|
operatorId: readString(item, 'operator_id'),
|
||||||
completedCount: completed,
|
operatorRole: readString(item, 'operator_role'),
|
||||||
exceptionCount: 0,
|
remark: readString(item, 'remark'),
|
||||||
expectedIncome: 0,
|
createdAt: readString(item, 'created_at')
|
||||||
onlineStatus: 'online',
|
}
|
||||||
nextOrder,
|
|
||||||
recentOrders: orders.slice(0, 5)
|
|
||||||
} as DeliveryDashboardType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrdersByTab(tab: string): Promise<Array<DeliveryOrderType>> {
|
function parseReview(item: any): ServiceReviewType {
|
||||||
const staffId = await getCurrentStaffId()
|
return {
|
||||||
if (staffId == '') {
|
id: readString(item, 'id'),
|
||||||
return [] as Array<DeliveryOrderType>
|
orderId: readString(item, 'order_id'),
|
||||||
|
userId: readString(item, 'user_id'),
|
||||||
|
rating: readNumber(item, 'rating') == 0 ? 5 : readNumber(item, 'rating'),
|
||||||
|
tags: safeParseArray(safeJsonField(item, 'tags_json')),
|
||||||
|
content: readString(item, 'content'),
|
||||||
|
createdAt: readString(item, 'created_at')
|
||||||
}
|
}
|
||||||
const response = await supa.from('hss_service_orders').select('*').eq('current_staff_id', staffId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (response.error != null || response.data == null) {
|
|
||||||
return [] as Array<DeliveryOrderType>
|
|
||||||
}
|
}
|
||||||
const rawOrders = response.data as any[]
|
|
||||||
const result = [] as Array<DeliveryOrderType>
|
function parseExecutionRecord(item: any): ServiceExecutionRecordType {
|
||||||
for (let i = 0; i < rawOrders.length; i++) {
|
return {
|
||||||
const orderObj = JSON.parse(JSON.stringify(rawOrders[i])) as UTSJSONObject
|
id: readString(item, 'id'),
|
||||||
const normalized = normalizeServiceOrderStatus(orderObj.getString('status') ?? '')
|
orderId: readString(item, 'order_id'),
|
||||||
let matched = true
|
assignmentId: readString(item, 'assignment_id'),
|
||||||
if (tab == 'pending') {
|
checkinTime: readString(item, 'checkin_time'),
|
||||||
matched = normalized == 'assigned'
|
checkinLatitude: readNumber(item, 'checkin_latitude'),
|
||||||
} else if (tab == 'today') {
|
checkinLongitude: readNumber(item, 'checkin_longitude'),
|
||||||
matched = normalized == 'assigned' || normalized == 'accepted' || normalized == 'departed' || normalized == 'arrived' || normalized == 'in_service' || normalized == 'pending_acceptance'
|
checkinAddress: readString(item, 'checkin_address'),
|
||||||
} else if (tab == 'history') {
|
serviceStartedAt: readString(item, 'service_started_at'),
|
||||||
matched = normalized == 'pending_acceptance' || normalized == 'accepted_by_user' || normalized == 'reviewed' || normalized == 'settled' || normalized == 'exception' || normalized == 'cancelled'
|
serviceFinishedAt: readString(item, 'service_finished_at'),
|
||||||
|
actualDurationMinutes: readNumber(item, 'actual_duration_minutes'),
|
||||||
|
serviceItemsJson: safeJsonField(item, 'service_items_json'),
|
||||||
|
summary: readString(item, 'summary'),
|
||||||
|
remark: readString(item, 'remark'),
|
||||||
|
trackPointsJson: safeJsonField(item, 'track_points_json'),
|
||||||
|
createdAt: readString(item, 'created_at'),
|
||||||
|
updatedAt: readString(item, 'updated_at')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEvidenceFile(item: any): ServiceEvidenceFileType {
|
||||||
|
return {
|
||||||
|
id: readString(item, 'id'),
|
||||||
|
orderId: readString(item, 'order_id'),
|
||||||
|
executionRecordId: readString(item, 'execution_record_id'),
|
||||||
|
phase: readString(item, 'phase'),
|
||||||
|
fileType: readString(item, 'file_type'),
|
||||||
|
storagePath: readString(item, 'storage_path'),
|
||||||
|
fileUrl: readString(item, 'file_url'),
|
||||||
|
latitude: readNumber(item, 'latitude'),
|
||||||
|
longitude: readNumber(item, 'longitude'),
|
||||||
|
capturedAt: readString(item, 'captured_at'),
|
||||||
|
createdAt: readString(item, 'created_at')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>, review: ServiceReviewType | null): ServiceOrderType {
|
||||||
|
const addressSnapshot = safeJsonField(item, 'address_snapshot_json')
|
||||||
|
const serviceSnapshot = safeJsonField(item, 'service_snapshot_json')
|
||||||
|
const addressObj = plainObject(safeParseObject(addressSnapshot))
|
||||||
|
const serviceObj = plainObject(safeParseObject(serviceSnapshot))
|
||||||
|
return {
|
||||||
|
id: readString(item, 'id'),
|
||||||
|
orderNo: readString(item, 'order_no'),
|
||||||
|
userId: readString(item, 'user_id'),
|
||||||
|
serviceId: readString(item, 'service_id'),
|
||||||
|
serviceName: readString(item, 'service_name'),
|
||||||
|
serviceSnapshot: {
|
||||||
|
serviceId: readString(serviceObj, 'serviceId') != '' ? readString(serviceObj, 'serviceId') : readString(item, 'service_id'),
|
||||||
|
serviceName: readString(serviceObj, 'serviceName') != '' ? readString(serviceObj, 'serviceName') : readString(item, 'service_name'),
|
||||||
|
category: readString(serviceObj, 'category'),
|
||||||
|
price: readNumber(serviceObj, 'price'),
|
||||||
|
durationText: readString(serviceObj, 'durationText'),
|
||||||
|
summary: readString(serviceObj, 'summary'),
|
||||||
|
tags: safeParseArray(safeJsonField(serviceObj, 'tags')),
|
||||||
|
suitableFor: readString(serviceObj, 'suitableFor')
|
||||||
|
},
|
||||||
|
serviceAddressId: readString(item, 'service_address_id'),
|
||||||
|
addressSnapshot: {
|
||||||
|
addressId: readString(addressObj, 'addressId'),
|
||||||
|
contactName: readString(addressObj, 'contactName'),
|
||||||
|
contactPhone: readString(addressObj, 'contactPhone'),
|
||||||
|
province: readString(addressObj, 'province'),
|
||||||
|
city: readString(addressObj, 'city'),
|
||||||
|
district: readString(addressObj, 'district'),
|
||||||
|
detailAddress: readString(addressObj, 'detailAddress'),
|
||||||
|
fullAddress: readString(addressObj, 'fullAddress'),
|
||||||
|
latitude: readNumber(addressObj, 'latitude'),
|
||||||
|
longitude: readNumber(addressObj, 'longitude'),
|
||||||
|
coordinateType: readString(addressObj, 'coordinateType') == '' ? 'gcj02' : readString(addressObj, 'coordinateType'),
|
||||||
|
remark: readString(addressObj, 'remark')
|
||||||
|
},
|
||||||
|
recipientName: readString(item, 'recipient_name'),
|
||||||
|
recipientPhone: readString(item, 'recipient_phone'),
|
||||||
|
contactName: readString(item, 'contact_name'),
|
||||||
|
contactPhone: readString(item, 'contact_phone'),
|
||||||
|
appointmentTime: readString(item, 'appointment_time'),
|
||||||
|
remark: readString(item, 'remark'),
|
||||||
|
status: normalizeServiceOrderStatus(readString(item, 'status')),
|
||||||
|
currentAssignmentId: readString(item, 'current_assignment_id'),
|
||||||
|
currentStaffId: readString(item, 'current_staff_id'),
|
||||||
|
acceptedAt: readString(item, 'accepted_at'),
|
||||||
|
departedAt: readString(item, 'departed_at'),
|
||||||
|
arrivedAt: readString(item, 'arrived_at'),
|
||||||
|
serviceStartedAt: readString(item, 'service_started_at'),
|
||||||
|
completedAt: readString(item, 'completed_at'),
|
||||||
|
pendingAcceptanceAt: readString(item, 'pending_acceptance_at'),
|
||||||
|
acceptedByUserAt: readString(item, 'accepted_by_user_at'),
|
||||||
|
reviewedAt: readString(item, 'reviewed_at'),
|
||||||
|
createdAt: readString(item, 'created_at'),
|
||||||
|
updatedAt: readString(item, 'updated_at'),
|
||||||
|
staffName: '',
|
||||||
|
staffPhone: '',
|
||||||
|
logs,
|
||||||
|
executionRecord: null,
|
||||||
|
evidenceFiles: [] as Array<any>,
|
||||||
|
review
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function insertStatusLog(orderId: string, fromStatus: string, toStatus: ServiceOrderStatus, operatorId: string, operatorRole: string, remark: string): Promise<void> {
|
||||||
|
await supa.from('hss_service_order_status_logs').insert({
|
||||||
|
id: buildId('slog'),
|
||||||
|
order_id: orderId,
|
||||||
|
from_status: fromStatus,
|
||||||
|
to_status: toStatus,
|
||||||
|
operator_id: operatorId == '' ? null : operatorId,
|
||||||
|
operator_role: operatorRole,
|
||||||
|
remark,
|
||||||
|
created_at: new Date().toISOString()
|
||||||
|
}).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildAddressSnapshot(address: UserAddress, latitude: number, longitude: number): ServiceOrderAddressSnapshotType {
|
||||||
|
return {
|
||||||
|
addressId: address.id,
|
||||||
|
contactName: address.recipient_name,
|
||||||
|
contactPhone: address.phone,
|
||||||
|
province: address.province,
|
||||||
|
city: address.city,
|
||||||
|
district: address.district,
|
||||||
|
detailAddress: address.detail_address,
|
||||||
|
fullAddress: address.province + address.city + address.district + ' ' + address.detail_address,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
coordinateType: 'gcj02',
|
||||||
|
remark: address.label ?? ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createServiceOrder(params: CreateServiceOrderParams): Promise<ServiceOrderType | null> {
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
if (userId == '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const orderId = buildId('so')
|
||||||
|
const orderNo = buildOrderNo()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
const appointmentTime = normalizeAppointmentTime(params.appointmentTime)
|
||||||
|
const response = await supa.from('hss_service_orders').insert({
|
||||||
|
id: orderId,
|
||||||
|
order_no: orderNo,
|
||||||
|
user_id: userId,
|
||||||
|
service_id: params.service.id,
|
||||||
|
service_name: params.service.name,
|
||||||
|
service_snapshot_json: params.service as any,
|
||||||
|
service_address_id: normalizeUuidOrNull(params.address.addressId),
|
||||||
|
address_snapshot_json: params.address as any,
|
||||||
|
recipient_name: params.recipientName,
|
||||||
|
recipient_phone: params.recipientPhone,
|
||||||
|
contact_name: params.contactName,
|
||||||
|
contact_phone: params.contactPhone,
|
||||||
|
appointment_time: appointmentTime,
|
||||||
|
remark: params.remark,
|
||||||
|
status: 'created',
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now
|
||||||
|
}).execute()
|
||||||
|
if (response.error != null) {
|
||||||
|
console.error('createServiceOrder failed', response.error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
await insertStatusLog(orderId, '', 'created', userId, 'consumer', '创建服务订单')
|
||||||
|
const staffResponse = await supa
|
||||||
|
.from('ml_delivery_staff')
|
||||||
|
.select('id, station_id, nickname, phone, status, deleted_at')
|
||||||
|
.eq('status', 1)
|
||||||
|
.order('created_at', { ascending: true })
|
||||||
|
.execute()
|
||||||
|
if (staffResponse.data != null) {
|
||||||
|
const rawStaffList = staffResponse.data as any[]
|
||||||
|
if (rawStaffList.length > 0) {
|
||||||
|
const staffObj = plainObject(rawStaffList[0])
|
||||||
|
const assignmentId = buildId('sa')
|
||||||
|
await supa.from('hss_service_assignments').insert({
|
||||||
|
id: assignmentId,
|
||||||
|
order_id: orderId,
|
||||||
|
staff_id: readString(staffObj, 'id'),
|
||||||
|
station_id: readString(staffObj, 'station_id') == '' ? null : readString(staffObj, 'station_id'),
|
||||||
|
status: 'assigned',
|
||||||
|
assigned_at: now,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now
|
||||||
|
}).execute()
|
||||||
|
await supa.from('hss_service_orders').update({
|
||||||
|
status: 'assigned',
|
||||||
|
current_assignment_id: assignmentId,
|
||||||
|
current_staff_id: readString(staffObj, 'id'),
|
||||||
|
updated_at: now
|
||||||
|
}).eq('id', orderId).execute()
|
||||||
|
await insertStatusLog(orderId, 'created', 'assigned', userId, 'system', '系统已自动派单')
|
||||||
}
|
}
|
||||||
if (tab == 'all' || matched) {
|
}
|
||||||
result.push(await parseDeliveryOrder(orderObj.getString('id') ?? '', rawOrders[i]))
|
return await getServiceOrderDetail(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listConsumerServiceOrders(): Promise<Array<ServiceOrderType>> {
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
if (userId == '') {
|
||||||
|
return [] as Array<ServiceOrderType>
|
||||||
|
}
|
||||||
|
const response = await supa.from('hss_service_orders').select('*').eq('user_id', userId).order('created_at', { ascending: false }).execute()
|
||||||
|
if (response.error != null || response.data == null) {
|
||||||
|
return [] as Array<ServiceOrderType>
|
||||||
|
}
|
||||||
|
const list = response.data as any[]
|
||||||
|
const result = [] as Array<ServiceOrderType>
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const parsed = await getServiceOrderDetail(JSON.parse(JSON.stringify(list[i]))['id'] as string)
|
||||||
|
if (parsed != null) {
|
||||||
|
result.push(parsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrderDetail(orderId: string): Promise<DeliveryOrderType | null> {
|
export async function getServiceOrderDetail(orderId: string): Promise<ServiceOrderType | null> {
|
||||||
const response = await supa.from('hss_service_orders').select('*').eq('id', orderId).single().execute()
|
const orderResponse = await supa.from('hss_service_orders').select('*').eq('id', orderId).limit(1).execute()
|
||||||
if (response.error != null || response.data == null) {
|
if (orderResponse.error != null || orderResponse.data == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return await parseDeliveryOrder(orderId, response.data)
|
const rawOrders = orderResponse.data as any[]
|
||||||
|
if (rawOrders.length == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const logsResponse = await supa.from('hss_service_order_status_logs').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
||||||
|
const reviewResponse = await supa.from('hss_service_reviews').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
||||||
|
const executionResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).limit(1).execute()
|
||||||
|
const evidenceResponse = await supa.from('hss_service_evidence_files').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
||||||
|
const logs = [] as Array<ServiceOrderTimelineItemType>
|
||||||
|
if (logsResponse.data != null) {
|
||||||
|
const rawLogs = logsResponse.data as any[]
|
||||||
|
for (let i = 0; i < rawLogs.length; i++) {
|
||||||
|
logs.push(parseTimeline(rawLogs[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let review: ServiceReviewType | null = null
|
||||||
|
if (reviewResponse.data != null) {
|
||||||
|
const rawReviews = reviewResponse.data as any[]
|
||||||
|
if (rawReviews.length > 0) {
|
||||||
|
review = parseReview(rawReviews[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const parsed = parseServiceOrder(rawOrders[0], logs, review)
|
||||||
|
if (executionResponse.data != null) {
|
||||||
|
const rawExecutionList = executionResponse.data as any[]
|
||||||
|
if (rawExecutionList.length > 0) {
|
||||||
|
parsed.executionRecord = parseExecutionRecord(rawExecutionList[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evidenceResponse.data != null) {
|
||||||
|
const rawEvidenceList = evidenceResponse.data as any[]
|
||||||
|
const evidenceFiles = [] as Array<ServiceEvidenceFileType>
|
||||||
|
for (let i = 0; i < rawEvidenceList.length; i++) {
|
||||||
|
evidenceFiles.push(parseEvidenceFile(rawEvidenceList[i]))
|
||||||
|
}
|
||||||
|
parsed.evidenceFiles = evidenceFiles
|
||||||
|
}
|
||||||
|
if (parsed.currentStaffId != '') {
|
||||||
|
const staffResponse = await supa.from('ml_delivery_staff').select('nickname, phone').eq('id', parsed.currentStaffId).limit(1).execute()
|
||||||
|
if (staffResponse.data != null) {
|
||||||
|
const rawStaffList = staffResponse.data as any[]
|
||||||
|
if (rawStaffList.length > 0) {
|
||||||
|
const staffObj = plainObject(rawStaffList[0])
|
||||||
|
parsed.staffName = readString(staffObj, 'nickname')
|
||||||
|
parsed.staffPhone = readString(staffObj, 'phone')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateOrderStatus(orderId: string, nextStatus: ServiceOrderStatus, updateData: UTSJSONObject, remark: string): Promise<DeliveryOrderType | null> {
|
export async function confirmServiceOrder(orderId: string, rating: number, content: string, tags: Array<string>): Promise<ServiceOrderType | null> {
|
||||||
const current = await getOrderDetail(orderId)
|
const userId = getCurrentUserId()
|
||||||
|
if (userId == '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const current = await getServiceOrderDetail(orderId)
|
||||||
if (current == null) {
|
if (current == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const map = JSON.parse('{}') as UTSJSONObject
|
const acceptedAt = new Date().toISOString()
|
||||||
map.set('status', nextStatus)
|
const updateResponse = await supa.from('hss_service_orders').update({
|
||||||
map.set('updated_at', nowIso())
|
status: 'accepted_by_user',
|
||||||
const iterator = updateData.keys()
|
accepted_by_user_at: acceptedAt,
|
||||||
while (iterator.hasNext()) {
|
updated_at: acceptedAt
|
||||||
const key = iterator.next()
|
}).eq('id', orderId).execute()
|
||||||
map.set(key, updateData.get(key))
|
if (updateResponse.error != null) {
|
||||||
}
|
|
||||||
const response = await supa.from('hss_service_orders').update(map).eq('id', orderId).execute()
|
|
||||||
if (response.error != null) {
|
|
||||||
console.error('updateOrderStatus failed', response.error)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
await insertStatusLog(orderId, normalizeServiceOrderStatus(current.status as string), nextStatus, remark)
|
await insertStatusLog(orderId, current.status, 'accepted_by_user', userId, 'consumer', '用户确认验收')
|
||||||
return await getOrderDetail(orderId)
|
await supa.from('hss_service_reviews').insert({
|
||||||
}
|
id: buildId('srv'),
|
||||||
|
|
||||||
export async function acceptOrder(orderId: string): Promise<DeliveryOrderType | null> {
|
|
||||||
const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (assignmentResponse.data != null) {
|
|
||||||
const raw = assignmentResponse.data as any[]
|
|
||||||
if (raw.length > 0) {
|
|
||||||
const assignmentId = JSON.parse(JSON.stringify(raw[0]))['id'] as string
|
|
||||||
await supa.from('hss_service_assignments').update({ status: 'accepted', accepted_at: nowIso(), updated_at: nowIso() }).eq('id', assignmentId).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const updateData = JSON.parse('{}') as UTSJSONObject
|
|
||||||
updateData.set('accepted_at', nowIso())
|
|
||||||
return await updateOrderStatus(orderId, 'accepted', updateData, '服务人员接单')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function departOrder(orderId: string, location: DeliveryLocationType | null): Promise<DeliveryOrderType | null> {
|
|
||||||
const updateData = JSON.parse('{}') as UTSJSONObject
|
|
||||||
updateData.set('departed_at', nowIso())
|
|
||||||
return await updateOrderStatus(orderId, 'departed', updateData, location == null ? '服务人员出发' : '服务人员出发:' + location.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function arriveOrder(orderId: string, location: DeliveryLocationType | null): Promise<DeliveryOrderType | null> {
|
|
||||||
const updateData = JSON.parse('{}') as UTSJSONObject
|
|
||||||
updateData.set('arrived_at', nowIso())
|
|
||||||
return await updateOrderStatus(orderId, 'arrived', updateData, location == null ? '服务人员到达' : '服务人员到达:' + location.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkinOrder(orderId: string, payload: DeliveryCheckinPayloadType): Promise<DeliveryOrderType | null> {
|
|
||||||
const current = await getOrderDetail(orderId)
|
|
||||||
if (current == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let assignmentId = ''
|
|
||||||
const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (assignmentResponse.data != null) {
|
|
||||||
const rawAssignments = assignmentResponse.data as any[]
|
|
||||||
if (rawAssignments.length > 0) {
|
|
||||||
assignmentId = JSON.parse(JSON.stringify(rawAssignments[0]))['id'] as string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let recordId = buildId('ser')
|
|
||||||
const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (recordResponse.data != null) {
|
|
||||||
const records = recordResponse.data as any[]
|
|
||||||
if (records.length > 0) {
|
|
||||||
recordId = JSON.parse(JSON.stringify(records[0]))['id'] as string
|
|
||||||
await supa.from('hss_service_execution_records').update({
|
|
||||||
checkin_time: nowIso(),
|
|
||||||
checkin_latitude: payload.location.latitude,
|
|
||||||
checkin_longitude: payload.location.longitude,
|
|
||||||
checkin_address: payload.location.address,
|
|
||||||
remark: payload.note,
|
|
||||||
updated_at: nowIso()
|
|
||||||
}).eq('id', recordId).execute()
|
|
||||||
} else {
|
|
||||||
await supa.from('hss_service_execution_records').insert({
|
|
||||||
id: recordId,
|
|
||||||
order_id: orderId,
|
|
||||||
assignment_id: assignmentId,
|
|
||||||
checkin_time: nowIso(),
|
|
||||||
checkin_latitude: payload.location.latitude,
|
|
||||||
checkin_longitude: payload.location.longitude,
|
|
||||||
checkin_address: payload.location.address,
|
|
||||||
remark: payload.note,
|
|
||||||
created_at: nowIso(),
|
|
||||||
updated_at: nowIso()
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < payload.photos.length; i++) {
|
|
||||||
await supa.from('hss_service_evidence_files').insert({
|
|
||||||
id: buildId('sef'),
|
|
||||||
order_id: orderId,
|
|
||||||
execution_record_id: recordId,
|
|
||||||
phase: 'checkin',
|
|
||||||
file_type: 'image',
|
|
||||||
storage_path: payload.photos[i],
|
|
||||||
file_url: payload.photos[i],
|
|
||||||
latitude: payload.location.latitude,
|
|
||||||
longitude: payload.location.longitude,
|
|
||||||
captured_at: nowIso(),
|
|
||||||
created_at: nowIso()
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startService(orderId: string): Promise<DeliveryOrderType | null> {
|
|
||||||
let recordId = ''
|
|
||||||
const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (recordResponse.data != null) {
|
|
||||||
const records = recordResponse.data as any[]
|
|
||||||
if (records.length > 0) {
|
|
||||||
recordId = JSON.parse(JSON.stringify(records[0]))['id'] as string
|
|
||||||
await supa.from('hss_service_execution_records').update({ service_started_at: nowIso(), updated_at: nowIso() }).eq('id', recordId).execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const updateData = JSON.parse('{}') as UTSJSONObject
|
|
||||||
updateData.set('service_started_at', nowIso())
|
|
||||||
return await updateOrderStatus(orderId, 'in_service', updateData, '开始服务')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveServiceRecord(orderId: string, record: DeliveryServiceRecordType): Promise<DeliveryOrderType | null> {
|
|
||||||
let assignmentId = ''
|
|
||||||
const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute()
|
|
||||||
if (assignmentResponse.data != null) {
|
|
||||||
const assignments = assignmentResponse.data as any[]
|
|
||||||
if (assignments.length > 0) {
|
|
||||||
assignmentId = JSON.parse(JSON.stringify(assignments[0]))['id'] as string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await supa.from('hss_service_execution_records').upsert({
|
|
||||||
id: record.id,
|
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
assignment_id: assignmentId,
|
user_id: userId,
|
||||||
service_started_at: record.startTime,
|
rating,
|
||||||
service_finished_at: record.endTime,
|
tags_json: tags as any,
|
||||||
actual_duration_minutes: record.actualDurationMinutes,
|
content,
|
||||||
service_items_json: record.serviceItems as any,
|
created_at: acceptedAt
|
||||||
summary: record.processNote,
|
|
||||||
remark: record.staffRemark,
|
|
||||||
updated_at: nowIso(),
|
|
||||||
created_at: record.createdAt
|
|
||||||
}).execute()
|
}).execute()
|
||||||
for (let i = 0; i < record.photos.length; i++) {
|
await supa.from('hss_service_orders').update({
|
||||||
await supa.from('hss_service_evidence_files').insert({
|
status: 'reviewed',
|
||||||
id: buildId('sef'),
|
reviewed_at: acceptedAt,
|
||||||
order_id: orderId,
|
updated_at: acceptedAt
|
||||||
execution_record_id: record.id,
|
}).eq('id', orderId).execute()
|
||||||
phase: 'service',
|
await insertStatusLog(orderId, 'accepted_by_user', 'reviewed', userId, 'consumer', '用户提交评价')
|
||||||
file_type: 'image',
|
return await getServiceOrderDetail(orderId)
|
||||||
storage_path: record.photos[i],
|
|
||||||
file_url: record.photos[i],
|
|
||||||
captured_at: nowIso(),
|
|
||||||
created_at: nowIso()
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
return await getOrderDetail(orderId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function finishOrder(orderId: string): Promise<DeliveryOrderType | null> {
|
export async function rejectServiceOrderAcceptance(orderId: string, content: string): Promise<ServiceOrderType | null> {
|
||||||
const updateData = JSON.parse('{}') as UTSJSONObject
|
const userId = getCurrentUserId()
|
||||||
updateData.set('completed_at', nowIso())
|
if (userId == '') {
|
||||||
updateData.set('pending_acceptance_at', nowIso())
|
return null
|
||||||
return await updateOrderStatus(orderId, 'pending_acceptance', updateData, '服务完成,等待用户验收')
|
}
|
||||||
|
const current = await getServiceOrderDetail(orderId)
|
||||||
|
if (current == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const updateResponse = await supa.from('hss_service_orders').update({
|
||||||
|
status: 'exception',
|
||||||
|
updated_at: nowIso()
|
||||||
|
}).eq('id', orderId).execute()
|
||||||
|
if (updateResponse.error != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
await insertStatusLog(orderId, current.status, 'exception', userId, 'consumer', content == '' ? '用户退回整改' : content)
|
||||||
|
return await getServiceOrderDetail(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentConsumerUser() {
|
||||||
|
return await getCurrentUser()
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,28 @@ export type HomeServiceTimelineItemType = {
|
|||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HomeServiceSelectedAddressType = {
|
||||||
|
addressId: string
|
||||||
|
userId: string
|
||||||
|
isDefault: boolean
|
||||||
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
phone?: string
|
||||||
|
addressName: string
|
||||||
|
locationName?: string
|
||||||
|
addressDetail: string
|
||||||
|
locationAddress?: string
|
||||||
|
houseNumber: string
|
||||||
|
doorNo?: string
|
||||||
|
fullAddress: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
remark: string
|
||||||
|
coordinateType: string
|
||||||
|
createdAt: number
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
|
|
||||||
export type HomeServiceCaseType = {
|
export type HomeServiceCaseType = {
|
||||||
id: string
|
id: string
|
||||||
caseNo: string
|
caseNo: string
|
||||||
@@ -35,6 +57,13 @@ export type HomeServiceCaseType = {
|
|||||||
staffName: string
|
staffName: string
|
||||||
staffPhone: string
|
staffPhone: string
|
||||||
amount: number
|
amount: number
|
||||||
|
checkinTime: string
|
||||||
|
checkinAddress: string
|
||||||
|
serviceStartedAt: string
|
||||||
|
serviceFinishedAt: string
|
||||||
|
executionSummary: string
|
||||||
|
evidenceCount: number
|
||||||
|
serviceAddressSnapshot?: HomeServiceSelectedAddressType | null
|
||||||
timeline: Array<HomeServiceTimelineItemType>
|
timeline: Array<HomeServiceTimelineItemType>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +96,7 @@ export type HomeServiceApplicationDraftType = {
|
|||||||
address: string
|
address: string
|
||||||
preferredTime: string
|
preferredTime: string
|
||||||
demandSummary: string
|
demandSummary: string
|
||||||
|
serviceAddressSnapshot?: HomeServiceSelectedAddressType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HomeServiceAdminApplicationType = {
|
export type HomeServiceAdminApplicationType = {
|
||||||
|
|||||||
@@ -33,6 +33,32 @@ export const SERVICE_ORDER_STATUS_LIST: Array<ServiceOrderStatus> = [
|
|||||||
'exception'
|
'exception'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export type ServiceOrderAddressSnapshotType = {
|
||||||
|
addressId: string
|
||||||
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
province: string
|
||||||
|
city: string
|
||||||
|
district: string
|
||||||
|
detailAddress: string
|
||||||
|
fullAddress: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
coordinateType: string
|
||||||
|
remark: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceOrderServiceSnapshotType = {
|
||||||
|
serviceId: string
|
||||||
|
serviceName: string
|
||||||
|
category: string
|
||||||
|
price: number
|
||||||
|
durationText: string
|
||||||
|
summary: string
|
||||||
|
tags: Array<string>
|
||||||
|
suitableFor: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ServiceOrderTimelineItemType = {
|
export type ServiceOrderTimelineItemType = {
|
||||||
id: string
|
id: string
|
||||||
orderId: string
|
orderId: string
|
||||||
@@ -44,6 +70,85 @@ export type ServiceOrderTimelineItemType = {
|
|||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ServiceExecutionRecordType = {
|
||||||
|
id: string
|
||||||
|
orderId: string
|
||||||
|
assignmentId: string
|
||||||
|
checkinTime: string
|
||||||
|
checkinLatitude: number
|
||||||
|
checkinLongitude: number
|
||||||
|
checkinAddress: string
|
||||||
|
serviceStartedAt: string
|
||||||
|
serviceFinishedAt: string
|
||||||
|
actualDurationMinutes: number
|
||||||
|
serviceItemsJson: string
|
||||||
|
summary: string
|
||||||
|
remark: string
|
||||||
|
trackPointsJson: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceEvidenceFileType = {
|
||||||
|
id: string
|
||||||
|
orderId: string
|
||||||
|
executionRecordId: string
|
||||||
|
phase: string
|
||||||
|
fileType: string
|
||||||
|
storagePath: string
|
||||||
|
fileUrl: string
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
capturedAt: string
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceReviewType = {
|
||||||
|
id: string
|
||||||
|
orderId: string
|
||||||
|
userId: string
|
||||||
|
rating: number
|
||||||
|
tags: Array<string>
|
||||||
|
content: string
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceOrderType = {
|
||||||
|
id: string
|
||||||
|
orderNo: string
|
||||||
|
userId: string
|
||||||
|
serviceId: string
|
||||||
|
serviceName: string
|
||||||
|
serviceSnapshot: ServiceOrderServiceSnapshotType
|
||||||
|
serviceAddressId: string
|
||||||
|
addressSnapshot: ServiceOrderAddressSnapshotType
|
||||||
|
recipientName: string
|
||||||
|
recipientPhone: string
|
||||||
|
contactName: string
|
||||||
|
contactPhone: string
|
||||||
|
appointmentTime: string
|
||||||
|
remark: string
|
||||||
|
status: ServiceOrderStatus
|
||||||
|
currentAssignmentId: string
|
||||||
|
currentStaffId: string
|
||||||
|
acceptedAt: string
|
||||||
|
departedAt: string
|
||||||
|
arrivedAt: string
|
||||||
|
serviceStartedAt: string
|
||||||
|
completedAt: string
|
||||||
|
pendingAcceptanceAt: string
|
||||||
|
acceptedByUserAt: string
|
||||||
|
reviewedAt: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
staffName: string
|
||||||
|
staffPhone: string
|
||||||
|
logs: Array<ServiceOrderTimelineItemType>
|
||||||
|
executionRecord: ServiceExecutionRecordType | null
|
||||||
|
evidenceFiles: Array<ServiceEvidenceFileType>
|
||||||
|
review: ServiceReviewType | null
|
||||||
|
}
|
||||||
|
|
||||||
export function getServiceOrderStatusText(status: ServiceOrderStatus): string {
|
export function getServiceOrderStatusText(status: ServiceOrderStatus): string {
|
||||||
if (status == 'created') return '待处理'
|
if (status == 'created') return '待处理'
|
||||||
if (status == 'paid') return '已支付'
|
if (status == 'paid') return '已支付'
|
||||||
|
|||||||
172
utils/orderStatus.uts
Normal file
172
utils/orderStatus.uts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
export type UnifiedOrderSource = 'goods' | 'service'
|
||||||
|
|
||||||
|
export type OrderStatusSource = {
|
||||||
|
source?: UnifiedOrderSource | string
|
||||||
|
order_status: number
|
||||||
|
payment_status: number
|
||||||
|
pay_expire_at: string
|
||||||
|
created_at: string
|
||||||
|
cancel_reason: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ORDER_STATUS_PENDING = 1
|
||||||
|
export const ORDER_STATUS_PAID_OR_SHIPPING = 2
|
||||||
|
export const ORDER_STATUS_RECEIVING = 3
|
||||||
|
export const ORDER_STATUS_COMPLETED = 4
|
||||||
|
export const ORDER_STATUS_CANCELLED = 5
|
||||||
|
export const ORDER_STATUS_REFUNDING = 6
|
||||||
|
export const ORDER_STATUS_REFUNDED = 7
|
||||||
|
export const ORDER_STATUS_TIMEOUT_LEGACY = 8
|
||||||
|
|
||||||
|
export const PAYMENT_STATUS_UNPAID = 1
|
||||||
|
export const PAYMENT_STATUS_PAID = 2
|
||||||
|
export const PAYMENT_STATUS_PARTIAL_REFUND = 3
|
||||||
|
export const PAYMENT_STATUS_REFUNDED = 4
|
||||||
|
export const PAYMENT_STATUS_TIMEOUT = 5
|
||||||
|
|
||||||
|
export const ORDER_PAY_TIMEOUT_SECONDS = 10 * 60
|
||||||
|
export const ORDER_TIMEOUT_CANCEL_REASON = '支付超时自动取消'
|
||||||
|
|
||||||
|
function parseDateMs(value: string): number {
|
||||||
|
if (value == '') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const date = new Date(value)
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return date.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsTimeoutReason(cancelReason: string): boolean {
|
||||||
|
return cancelReason.indexOf('超时') >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalizedSource(order: OrderStatusSource): UnifiedOrderSource {
|
||||||
|
if (order.source == 'service') {
|
||||||
|
return 'service'
|
||||||
|
}
|
||||||
|
return 'goods'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPendingSourceOrder(order: OrderStatusSource): boolean {
|
||||||
|
return order.order_status == ORDER_STATUS_PENDING && order.payment_status == PAYMENT_STATUS_UNPAID
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderDeadlineMs(order: OrderStatusSource): number {
|
||||||
|
const payExpireAtMs = parseDateMs(order.pay_expire_at)
|
||||||
|
if (payExpireAtMs > 0) {
|
||||||
|
return payExpireAtMs
|
||||||
|
}
|
||||||
|
const createdAtMs = parseDateMs(order.created_at)
|
||||||
|
if (createdAtMs > 0) {
|
||||||
|
return createdAtMs + ORDER_PAY_TIMEOUT_SECONDS * 1000
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRemainingSeconds(order: OrderStatusSource): number {
|
||||||
|
const deadlineMs = getOrderDeadlineMs(order)
|
||||||
|
if (deadlineMs <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const diff = Math.floor((deadlineMs - Date.now()) / 1000)
|
||||||
|
return diff > 0 ? diff : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUnifiedDisplayState(order: OrderStatusSource): string {
|
||||||
|
const source = getNormalizedSource(order)
|
||||||
|
const deadlineMs = getOrderDeadlineMs(order)
|
||||||
|
const hasExpiredByDeadline = order.order_status == ORDER_STATUS_PENDING && deadlineMs > 0 && deadlineMs <= Date.now()
|
||||||
|
|
||||||
|
if (source == 'service') {
|
||||||
|
if (order.order_status == ORDER_STATUS_PENDING && order.payment_status == PAYMENT_STATUS_UNPAID && !hasExpiredByDeadline && !containsTimeoutReason(order.cancel_reason)) {
|
||||||
|
return 'pending_pay'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_TIMEOUT_LEGACY || order.payment_status == PAYMENT_STATUS_TIMEOUT || containsTimeoutReason(order.cancel_reason)) {
|
||||||
|
return 'cancelled'
|
||||||
|
}
|
||||||
|
if (hasExpiredByDeadline) {
|
||||||
|
return 'expired'
|
||||||
|
}
|
||||||
|
if (order.order_status == 2 || order.order_status == 3 || order.order_status == 4) {
|
||||||
|
return 'processing'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_COMPLETED) {
|
||||||
|
return 'completed'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_REFUNDING || order.order_status == ORDER_STATUS_REFUNDED) {
|
||||||
|
return 'refund'
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.order_status == ORDER_STATUS_PENDING && order.payment_status == PAYMENT_STATUS_UNPAID && !hasExpiredByDeadline && !containsTimeoutReason(order.cancel_reason)) {
|
||||||
|
return 'pending_pay'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_CANCELLED || order.order_status == ORDER_STATUS_TIMEOUT_LEGACY || order.payment_status == PAYMENT_STATUS_TIMEOUT || containsTimeoutReason(order.cancel_reason)) {
|
||||||
|
return 'cancelled'
|
||||||
|
}
|
||||||
|
if (hasExpiredByDeadline) {
|
||||||
|
return 'expired'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_PAID_OR_SHIPPING || order.order_status == ORDER_STATUS_RECEIVING) {
|
||||||
|
return 'processing'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_COMPLETED) {
|
||||||
|
return 'completed'
|
||||||
|
}
|
||||||
|
if (order.order_status == ORDER_STATUS_REFUNDING || order.order_status == ORDER_STATUS_REFUNDED) {
|
||||||
|
return 'refund'
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOrderPayExpired(order: OrderStatusSource): boolean {
|
||||||
|
const displayState = getUnifiedDisplayState(order)
|
||||||
|
return displayState == 'cancelled' || displayState == 'expired'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPendingPayOrder(order: OrderStatusSource): boolean {
|
||||||
|
return getUnifiedDisplayState(order) == 'pending_pay' && isPendingSourceOrder(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderDisplayStatus(order: OrderStatusSource): string {
|
||||||
|
const displayState = getUnifiedDisplayState(order)
|
||||||
|
if (displayState == 'pending_pay') {
|
||||||
|
return 'pending'
|
||||||
|
}
|
||||||
|
if (displayState == 'cancelled' || displayState == 'expired') {
|
||||||
|
return 'cancelled'
|
||||||
|
}
|
||||||
|
if (displayState == 'processing') {
|
||||||
|
return 'shipping'
|
||||||
|
}
|
||||||
|
if (displayState == 'completed') {
|
||||||
|
return 'completed'
|
||||||
|
}
|
||||||
|
if (displayState == 'refund') {
|
||||||
|
return 'refund'
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad2(value: number): string {
|
||||||
|
return value < 10 ? '0' + value : '' + value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCountdownHM(seconds: number): string {
|
||||||
|
const safeSeconds = seconds > 0 ? seconds : 0
|
||||||
|
const totalMinutes = Math.floor(safeSeconds / 60)
|
||||||
|
const hours = Math.floor(totalMinutes / 60)
|
||||||
|
const minutes = totalMinutes % 60
|
||||||
|
return pad2(hours) + '时' + pad2(minutes) + '分钟'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCountdownHMS(seconds: number): string {
|
||||||
|
const safeSeconds = seconds > 0 ? seconds : 0
|
||||||
|
const hours = Math.floor(safeSeconds / 3600)
|
||||||
|
const minutes = Math.floor((safeSeconds % 3600) / 60)
|
||||||
|
const remainSeconds = safeSeconds % 60
|
||||||
|
return pad2(hours) + ':' + pad2(minutes) + ':' + pad2(remainSeconds)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
685
报错信息.txt
685
报错信息.txt
@@ -1,3 +1,4 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
uni.api.esm.js:1042 POST http://192.168.1.62:18000/auth/v1/token?grant_type=password 401 (Unauthorized)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
uni.api.esm.js:1042 POST http://192.168.1.62:18000/auth/v1/token?grant_type=password 401 (Unauthorized)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
(anonymous) @ uni.api.esm.js:1042
|
(anonymous) @ uni.api.esm.js:1042
|
||||||
invokeApi @ uni.api.esm.js:330
|
invokeApi @ uni.api.esm.js:330
|
||||||
@@ -12,239 +13,579 @@ s @ regeneratorRuntime.js?forceSync=true:1
|
|||||||
_ @ regeneratorRuntime.js?forceSync=true:1
|
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
=======
|
||||||
|
mp.esm.js:529 [getOrderDetail] 开始获取订单详情,orderId: so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.error: null
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.data: []
|
||||||
|
mp.esm.js:529 [getOrderDetail] 未找到订单
|
||||||
|
mp.esm.js:529 [confirmPayment] 开始支付, orderId: so-1779679148063-14086 method: wechat
|
||||||
|
mp.esm.js:529 [getOrderDetail] 开始获取订单详情,orderId: so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.error: null
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.data: []
|
||||||
|
mp.esm.js:529 [getOrderDetail] 未找到订单
|
||||||
|
mp.esm.js:529 [payOrder] 订单不存在,无法支付(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee73$ @ supabaseService.uts:5491
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
request @ ak-req.uts:148
|
||||||
|
_callee9$ @ aksupa.uts:887
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
signIn @ aksupa.uts:876
|
||||||
|
_callee3$ @ delivery.uts:1506
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
loginDelivery @ delivery.uts:1500
|
||||||
|
_callee$ @ deliveryService.uts:42
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
loginDelivery @ deliveryService.uts:41
|
||||||
|
_callee5$ @ login.uvue:495
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
handleLogin @ login.uvue:477
|
||||||
|
=======
|
||||||
|
payOrder @ supabaseService.uts:5481
|
||||||
|
_callee6$ @ payment.uvue:1291
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
confirmPayment @ payment.uvue:1232
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
<<<<<<< HEAD
|
||||||
|
Show 19 more frames
|
||||||
|
mp.esm.js:529 [ak-req] ★ 401 Unauthorized(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee2$ @ ak-req.uts:336
|
||||||
|
=======
|
||||||
|
mp.esm.js:529 [confirmPayment] 支付结果: false
|
||||||
|
mp.esm.js:529 [confirmPayment] payOrder 返回 false(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee6$ @ payment.uvue:1295
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
request @ ak-req.uts:148
|
||||||
|
_callee9$ @ aksupa.uts:887
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
signIn @ aksupa.uts:876
|
||||||
|
_callee3$ @ delivery.uts:1506
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
loginDelivery @ delivery.uts:1500
|
||||||
|
_callee$ @ deliveryService.uts:42
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
loginDelivery @ deliveryService.uts:41
|
||||||
|
_callee5$ @ login.uvue:495
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
handleLogin @ login.uvue:477
|
||||||
|
=======
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
confirmPayment @ payment.uvue:1232
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
<<<<<<< HEAD
|
||||||
|
Show 14 more frames
|
||||||
|
mp.esm.js:529 [ak-req] url: http://192.168.1.62:18000/auth/v1/token?grant_type=password(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee2$ @ ak-req.uts:337
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
=======
|
||||||
|
mp.esm.js:529 [getOrderDetail] 开始获取订单详情,orderId: so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.error: null
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.data: []
|
||||||
|
mp.esm.js:529 [getOrderDetail] 未找到订单
|
||||||
|
mp.esm.js:529 [getOrderDetail] 开始获取订单详情,orderId: so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.error: null
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.data: []
|
||||||
|
mp.esm.js:529 [getOrderDetail] 未找到订单
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: return=representation
|
||||||
|
uni.api.esm.js:1042 PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ uni.api.esm.js:1042
|
||||||
|
invokeApi @ uni.api.esm.js:330
|
||||||
|
promiseApi @ uni.api.esm.js:889
|
||||||
|
(anonymous) @ ak-req.uts:214
|
||||||
|
doOnce @ ak-req.uts:213
|
||||||
|
_loop$ @ ak-req.uts:312
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
request @ ak-req.uts:148
|
||||||
|
<<<<<<< HEAD
|
||||||
|
_callee9$ @ aksupa.uts:887
|
||||||
|
=======
|
||||||
|
_callee19$ @ aksupa.uts:1288
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
signIn @ aksupa.uts:876
|
||||||
|
_callee3$ @ delivery.uts:1506
|
||||||
|
=======
|
||||||
|
requestWithAutoRefresh @ aksupa.uts:1287
|
||||||
|
_callee14$ @ aksupa.uts:1141
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
loginDelivery @ delivery.uts:1500
|
||||||
|
_callee$ @ deliveryService.uts:42
|
||||||
|
=======
|
||||||
|
update @ aksupa.uts:1117
|
||||||
|
_callee$ @ aksupa.uts:477
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
loginDelivery @ deliveryService.uts:41
|
||||||
|
_callee5$ @ login.uvue:495
|
||||||
|
=======
|
||||||
|
execute @ aksupa.uts:369
|
||||||
|
_callee61$ @ supabaseService.uts:4508
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
handleLogin @ login.uvue:477
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
Show 14 more frames
|
||||||
|
mp.esm.js:529 [ak-req] auth-mode: apikey-only(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee2$ @ ak-req.uts:338
|
||||||
|
=======
|
||||||
|
markOrderPaymentCancelled @ supabaseService.uts:4491
|
||||||
|
_callee6$ @ payment.uvue:1271
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
request @ ak-req.uts:148
|
||||||
|
_callee9$ @ aksupa.uts:887
|
||||||
|
=======
|
||||||
|
confirmPayment @ payment.uvue:1232
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
Show 21 more frames
|
||||||
|
mp.esm.js:529 [markOrderPaymentCancelled] 保留待付款订单失败: UniError: 请求失败: 400
|
||||||
|
at _construct (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/construct.js?forceSync=true:1:1227)
|
||||||
|
at new r (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/wrapNativeSuper.js?forceSync=true:1:1357)
|
||||||
|
at UniError2.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/createSuper.js?forceSync=true:1:1176)
|
||||||
|
at new UniError2 (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:890:22)
|
||||||
|
at Object.toUniError (weapp:///http://127.0.0.1:60394/appservice/utils/utils.js?t=wechat&s=1779670571338&v=44243ae0f2468b2c481e39a715a29204:77:18)
|
||||||
|
at AkSupa._callee19$ (weapp:///http://127.0.0.1:60394/appservice/components/supadb/aksupa.js?t=wechat&s=1779670571338&v=f87225c7384edc68270412f64fbd4e8a:1959:41)
|
||||||
|
at s (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1588)
|
||||||
|
at Generator.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:2925)
|
||||||
|
at Generator.next (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1951)
|
||||||
|
at fulfilled (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:10009:24)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee61$ @ supabaseService.uts:4511
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
markOrderPaymentCancelled @ supabaseService.uts:4491
|
||||||
|
_callee6$ @ payment.uvue:1271
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
|
Promise.then (async)
|
||||||
|
step @ uni.mp.esm.js:1134
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
confirmPayment @ payment.uvue:1232
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
mp.esm.js:529 收到订单更新事件: UTSJSONObject {orderId: "so-1779679148063-14086", status: 1, paymentStatus: 1, cancelReason: "", payExpireAt: "", …}
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_user_profiles filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_user_coupons filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&status=eq.1&expire_at=gt.2026-05-25T03%3A20%3A04.256Z
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_user_profiles?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)%2C%20ml_shops(shop_name)&order=created_at.desc&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: (none)
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_user_coupons?select=id&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&status=eq.1&expire_at=gt.2026-05-25T03%3A20%3A04.256Z
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_user_balance filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_user_balance?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.1
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.1
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_user_points filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_user_points?select=points&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact,return=representation,single-object
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.2
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.2
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.3
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.3
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.4
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.4
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=in.(6,7)
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=in.(6,7)
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrders] response.error: null
|
||||||
|
mp.esm.js:529 [getOrders] 订单数量: 104
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.5
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_status=eq.5
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_orders?select=status&limit=500&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] 开始获取订单详情,orderId: so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_orders?select=*%2C%20ml_order_items(*)&limit=1&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c&consumer_deleted_at=is.null&order_no=eq.so-1779679148063-14086
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: count=exact
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.error: null
|
||||||
|
mp.esm.js:529 [getOrderDetail] response.data: []
|
||||||
|
mp.esm.js:529 [getOrderDetail] 未找到订单
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: return=representation
|
||||||
|
uni.api.esm.js:1042 PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ uni.api.esm.js:1042
|
||||||
|
invokeApi @ uni.api.esm.js:330
|
||||||
|
promiseApi @ uni.api.esm.js:889
|
||||||
|
(anonymous) @ ak-req.uts:214
|
||||||
|
doOnce @ ak-req.uts:213
|
||||||
|
_loop$ @ ak-req.uts:312
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
Promise.then (async)
|
Promise.then (async)
|
||||||
step @ uni.mp.esm.js:1134
|
step @ uni.mp.esm.js:1134
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
request @ ak-req.uts:148
|
request @ ak-req.uts:148
|
||||||
_callee9$ @ aksupa.uts:887
|
_callee19$ @ aksupa.uts:1288
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
signIn @ aksupa.uts:876
|
|
||||||
_callee3$ @ delivery.uts:1506
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ delivery.uts:1500
|
|
||||||
_callee$ @ deliveryService.uts:42
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ deliveryService.uts:41
|
|
||||||
_callee5$ @ login.uvue:495
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
handleLogin @ login.uvue:477
|
|
||||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
|
||||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
|
||||||
invoke @ vue.runtime.esm.js:6223
|
|
||||||
setTimeout (async)
|
|
||||||
invoker @ vue.runtime.esm.js:6232
|
|
||||||
Show 19 more frames
|
|
||||||
mp.esm.js:529 [ak-req] ★ 401 Unauthorized(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
_callee2$ @ ak-req.uts:336
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
request @ ak-req.uts:148
|
|
||||||
_callee9$ @ aksupa.uts:887
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
signIn @ aksupa.uts:876
|
|
||||||
_callee3$ @ delivery.uts:1506
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ delivery.uts:1500
|
|
||||||
_callee$ @ deliveryService.uts:42
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ deliveryService.uts:41
|
|
||||||
_callee5$ @ login.uvue:495
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
handleLogin @ login.uvue:477
|
|
||||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
|
||||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
|
||||||
invoke @ vue.runtime.esm.js:6223
|
|
||||||
setTimeout (async)
|
|
||||||
invoker @ vue.runtime.esm.js:6232
|
|
||||||
Show 14 more frames
|
|
||||||
mp.esm.js:529 [ak-req] url: http://192.168.1.62:18000/auth/v1/token?grant_type=password(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
_callee2$ @ ak-req.uts:337
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
request @ ak-req.uts:148
|
|
||||||
_callee9$ @ aksupa.uts:887
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
signIn @ aksupa.uts:876
|
|
||||||
_callee3$ @ delivery.uts:1506
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ delivery.uts:1500
|
|
||||||
_callee$ @ deliveryService.uts:42
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
loginDelivery @ deliveryService.uts:41
|
|
||||||
_callee5$ @ login.uvue:495
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
handleLogin @ login.uvue:477
|
|
||||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
|
||||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
|
||||||
invoke @ vue.runtime.esm.js:6223
|
|
||||||
setTimeout (async)
|
|
||||||
invoker @ vue.runtime.esm.js:6232
|
|
||||||
Show 14 more frames
|
|
||||||
mp.esm.js:529 [ak-req] auth-mode: apikey-only(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
_callee2$ @ ak-req.uts:338
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
|
||||||
Promise.then (async)
|
|
||||||
step @ uni.mp.esm.js:1134
|
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
|
||||||
request @ ak-req.uts:148
|
|
||||||
_callee9$ @ aksupa.uts:887
|
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
signIn @ aksupa.uts:876
|
signIn @ aksupa.uts:876
|
||||||
_callee3$ @ delivery.uts:1506
|
_callee3$ @ delivery.uts:1506
|
||||||
|
=======
|
||||||
|
requestWithAutoRefresh @ aksupa.uts:1287
|
||||||
|
_callee14$ @ aksupa.uts:1141
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
loginDelivery @ delivery.uts:1500
|
loginDelivery @ delivery.uts:1500
|
||||||
_callee$ @ deliveryService.uts:42
|
_callee$ @ deliveryService.uts:42
|
||||||
|
=======
|
||||||
|
update @ aksupa.uts:1117
|
||||||
|
_callee$ @ aksupa.uts:477
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
loginDelivery @ deliveryService.uts:41
|
loginDelivery @ deliveryService.uts:41
|
||||||
_callee5$ @ login.uvue:495
|
_callee5$ @ login.uvue:495
|
||||||
|
=======
|
||||||
|
execute @ aksupa.uts:369
|
||||||
|
_callee52$ @ supabaseService.uts:4038
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
handleLogin @ login.uvue:477
|
handleLogin @ login.uvue:477
|
||||||
|
=======
|
||||||
|
cancelOrder @ supabaseService.uts:4018
|
||||||
|
_callee5$ @ payment.uvue:1153
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
confirmCancelOrder @ payment.uvue:1141
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
invoke @ vue.runtime.esm.js:6223
|
invoke @ vue.runtime.esm.js:6223
|
||||||
setTimeout (async)
|
setTimeout (async)
|
||||||
invoker @ vue.runtime.esm.js:6232
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
<<<<<<< HEAD
|
||||||
Show 14 more frames
|
Show 14 more frames
|
||||||
mp.esm.js:529 [ak-req] 发送 apikey: eyJhbG...7890(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
mp.esm.js:529 [ak-req] 发送 apikey: eyJhbG...7890(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
(anonymous) @ mp.esm.js:529
|
(anonymous) @ mp.esm.js:529
|
||||||
__f__ @ uni.api.esm.js:590
|
__f__ @ uni.api.esm.js:590
|
||||||
_callee2$ @ ak-req.uts:339
|
_callee2$ @ ak-req.uts:339
|
||||||
|
=======
|
||||||
|
Show 26 more frames
|
||||||
|
mp.esm.js:529 取消订单失败: UniError: 请求失败: 400
|
||||||
|
at _construct (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/construct.js?forceSync=true:1:1227)
|
||||||
|
at new r (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/wrapNativeSuper.js?forceSync=true:1:1357)
|
||||||
|
at UniError2.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/createSuper.js?forceSync=true:1:1176)
|
||||||
|
at new UniError2 (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:890:22)
|
||||||
|
at Object.toUniError (weapp:///http://127.0.0.1:60394/appservice/utils/utils.js?t=wechat&s=1779670571338&v=44243ae0f2468b2c481e39a715a29204:77:18)
|
||||||
|
at AkSupa._callee19$ (weapp:///http://127.0.0.1:60394/appservice/components/supadb/aksupa.js?t=wechat&s=1779670571338&v=f87225c7384edc68270412f64fbd4e8a:1959:41)
|
||||||
|
at s (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1588)
|
||||||
|
at Generator.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:2925)
|
||||||
|
at Generator.next (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1951)
|
||||||
|
at fulfilled (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:10009:24)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee52$ @ supabaseService.uts:4041
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
Promise.then (async)
|
Promise.then (async)
|
||||||
step @ uni.mp.esm.js:1134
|
step @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
cancelOrder @ supabaseService.uts:4018
|
||||||
|
_callee5$ @ payment.uvue:1153
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
confirmCancelOrder @ payment.uvue:1141
|
||||||
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
|
invoke @ vue.runtime.esm.js:6223
|
||||||
|
setTimeout (async)
|
||||||
|
invoker @ vue.runtime.esm.js:6232
|
||||||
|
[自动热重载] 已开启代码文件保存后自动热重载
|
||||||
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_orders filter: id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
||||||
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...YFxs | auth-mode: pre-set | prefer: return=representation
|
||||||
|
uni.api.esm.js:1042 PATCH http://119.146.131.237:9126/rest/v1/ml_orders?id=eq.so-1779679148063-14086&user_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ uni.api.esm.js:1042
|
||||||
|
invokeApi @ uni.api.esm.js:330
|
||||||
|
promiseApi @ uni.api.esm.js:889
|
||||||
|
(anonymous) @ ak-req.uts:214
|
||||||
|
doOnce @ ak-req.uts:213
|
||||||
|
_loop$ @ ak-req.uts:312
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
Promise.then (async)
|
Promise.then (async)
|
||||||
step @ uni.mp.esm.js:1134
|
step @ uni.mp.esm.js:1134
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
request @ ak-req.uts:148
|
request @ ak-req.uts:148
|
||||||
|
<<<<<<< HEAD
|
||||||
_callee9$ @ aksupa.uts:887
|
_callee9$ @ aksupa.uts:887
|
||||||
|
=======
|
||||||
|
_callee19$ @ aksupa.uts:1288
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
signIn @ aksupa.uts:876
|
signIn @ aksupa.uts:876
|
||||||
_callee3$ @ delivery.uts:1506
|
_callee3$ @ delivery.uts:1506
|
||||||
|
=======
|
||||||
|
requestWithAutoRefresh @ aksupa.uts:1287
|
||||||
|
_callee14$ @ aksupa.uts:1141
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
loginDelivery @ delivery.uts:1500
|
loginDelivery @ delivery.uts:1500
|
||||||
_callee$ @ deliveryService.uts:42
|
_callee$ @ deliveryService.uts:42
|
||||||
|
=======
|
||||||
|
update @ aksupa.uts:1117
|
||||||
|
_callee$ @ aksupa.uts:477
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
loginDelivery @ deliveryService.uts:41
|
loginDelivery @ deliveryService.uts:41
|
||||||
_callee5$ @ login.uvue:495
|
_callee5$ @ login.uvue:495
|
||||||
|
=======
|
||||||
|
execute @ aksupa.uts:369
|
||||||
|
_callee52$ @ supabaseService.uts:4038
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
handleLogin @ login.uvue:477
|
handleLogin @ login.uvue:477
|
||||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||||
@@ -256,12 +597,40 @@ mp.esm.js:529 [ak-req] 发送 Authorization: (MISSING!)(env: Windows,mp,1.06.250
|
|||||||
(anonymous) @ mp.esm.js:529
|
(anonymous) @ mp.esm.js:529
|
||||||
__f__ @ uni.api.esm.js:590
|
__f__ @ uni.api.esm.js:590
|
||||||
_callee2$ @ ak-req.uts:340
|
_callee2$ @ ak-req.uts:340
|
||||||
|
=======
|
||||||
|
cancelOrder @ supabaseService.uts:4018
|
||||||
|
_callee7$ @ orders.uvue:1422
|
||||||
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
doCancelOrder @ orders.uvue:1417
|
||||||
|
success @ orders.uvue:1443
|
||||||
|
(anonymous) @ uni.api.esm.js:946
|
||||||
|
Show 25 more frames
|
||||||
|
mp.esm.js:529 取消订单失败: UniError: 请求失败: 400
|
||||||
|
at _construct (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/construct.js?forceSync=true:1:1227)
|
||||||
|
at new r (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/wrapNativeSuper.js?forceSync=true:1:1357)
|
||||||
|
at UniError2.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/createSuper.js?forceSync=true:1:1176)
|
||||||
|
at new UniError2 (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:890:22)
|
||||||
|
at Object.toUniError (weapp:///http://127.0.0.1:60394/appservice/utils/utils.js?t=wechat&s=1779670571338&v=44243ae0f2468b2c481e39a715a29204:77:18)
|
||||||
|
at AkSupa._callee19$ (weapp:///http://127.0.0.1:60394/appservice/components/supadb/aksupa.js?t=wechat&s=1779670571338&v=f87225c7384edc68270412f64fbd4e8a:1959:41)
|
||||||
|
at s (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1588)
|
||||||
|
at Generator.<anonymous> (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:2925)
|
||||||
|
at Generator.next (weapp:///http://127.0.0.1:60394/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1951)
|
||||||
|
at fulfilled (weapp:///http://127.0.0.1:60394/appservice/common/vendor.js?t=wechat&s=1779670571338&v=2c3f4623fbd33b44aa0c0065a9eb296b:10009:24)(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
(anonymous) @ mp.esm.js:529
|
||||||
|
__f__ @ uni.api.esm.js:590
|
||||||
|
_callee52$ @ supabaseService.uts:4041
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
Promise.then (async)
|
Promise.then (async)
|
||||||
step @ uni.mp.esm.js:1134
|
step @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
fulfilled @ uni.mp.esm.js:1134
|
||||||
Promise.then (async)
|
Promise.then (async)
|
||||||
step @ uni.mp.esm.js:1134
|
step @ uni.mp.esm.js:1134
|
||||||
@@ -269,11 +638,18 @@ step @ uni.mp.esm.js:1134
|
|||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
request @ ak-req.uts:148
|
request @ ak-req.uts:148
|
||||||
_callee9$ @ aksupa.uts:887
|
_callee9$ @ aksupa.uts:887
|
||||||
|
=======
|
||||||
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
cancelOrder @ supabaseService.uts:4018
|
||||||
|
_callee7$ @ orders.uvue:1422
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
(anonymous) @ uni.mp.esm.js:1134
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
__awaiter @ uni.mp.esm.js:1134
|
||||||
|
<<<<<<< HEAD
|
||||||
signIn @ aksupa.uts:876
|
signIn @ aksupa.uts:876
|
||||||
_callee3$ @ delivery.uts:1506
|
_callee3$ @ delivery.uts:1506
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
s @ regeneratorRuntime.js?forceSync=true:1
|
||||||
@@ -459,4 +835,9 @@ Error: timeout
|
|||||||
at WAServiceMainContext.js?t=wechat&v=3.15.2:1(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
at WAServiceMainContext.js?t=wechat&v=3.15.2:1(env: Windows,mp,1.06.2504030; lib: 3.15.2)
|
||||||
|
|
||||||
15:42:21.953 [plugin:uts] Invalid end tag.
|
15:42:21.953 [plugin:uts] Invalid end tag.
|
||||||
15:42:22.203 at pages/mall/delivery/home/index.uvue:454:1
|
15:42:22.203 at pages/mall/delivery/home/index.uvue:454:1
|
||||||
|
=======
|
||||||
|
doCancelOrder @ orders.uvue:1417
|
||||||
|
success @ orders.uvue:1443
|
||||||
|
(anonymous) @ uni.api.esm.js:946
|
||||||
|
>>>>>>> d9103c9bf (完善下单逻辑及其ui展示,修复支付倒计时显示错误bug)
|
||||||
|
|||||||
Reference in New Issue
Block a user