添加首页加载skeleton
This commit is contained in:
368
components/home-skeleton/home-skeleton.uvue
Normal file
368
components/home-skeleton/home-skeleton.uvue
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
<template>
|
||||||
|
<view class="home-skeleton-root">
|
||||||
|
<view class="home-skeleton-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="home-skeleton-tab-row" :style="capsuleStyle">
|
||||||
|
<view class="home-skeleton-tab home-skeleton-tab-active skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-tab skeleton-shimmer"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-search skeleton-shimmer">
|
||||||
|
<view class="home-skeleton-search-icon"></view>
|
||||||
|
<view class="home-skeleton-search-line"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view
|
||||||
|
v-if="showCategoryBar"
|
||||||
|
class="home-skeleton-category-scroll"
|
||||||
|
direction="horizontal"
|
||||||
|
:scroll-x="true"
|
||||||
|
:show-scrollbar="false"
|
||||||
|
>
|
||||||
|
<view class="home-skeleton-category-row">
|
||||||
|
<view
|
||||||
|
v-for="slot in categorySlots"
|
||||||
|
:key="'category-slot-' + slot"
|
||||||
|
:class="['home-skeleton-category-pill', 'skeleton-shimmer', slot == 0 ? 'home-skeleton-category-pill-active' : '']"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-body">
|
||||||
|
<view class="home-skeleton-banner skeleton-shimmer"></view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-channel-grid">
|
||||||
|
<view
|
||||||
|
v-for="slot in channelSlots"
|
||||||
|
:key="'channel-slot-' + slot"
|
||||||
|
class="home-skeleton-channel-card"
|
||||||
|
>
|
||||||
|
<view class="home-skeleton-channel-icon skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-channel-line skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-channel-line home-skeleton-channel-line-short skeleton-shimmer"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-feed-header">
|
||||||
|
<view class="home-skeleton-feed-title skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-feed-subtitle skeleton-shimmer"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-product-grid">
|
||||||
|
<view
|
||||||
|
v-for="slot in productSlots"
|
||||||
|
:key="'product-slot-' + slot"
|
||||||
|
class="home-skeleton-product-card"
|
||||||
|
>
|
||||||
|
<view class="home-skeleton-product-image skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-product-body">
|
||||||
|
<view class="home-skeleton-product-line skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-product-line home-skeleton-product-line-short skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-product-price-row">
|
||||||
|
<view class="home-skeleton-product-price skeleton-shimmer"></view>
|
||||||
|
<view class="home-skeleton-product-origin skeleton-shimmer"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="home-skeleton-safe-bottom" :style="{ height: bottomSafeArea + 88 + 'px' }"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
statusBarHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
capsuleRight: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
bottomSafeArea: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
showCategoryBar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
categoryCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 7
|
||||||
|
},
|
||||||
|
channelCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
productCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 8
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const capsuleStyle = computed((): string => props.capsuleRight > 0 ? `padding-right:${props.capsuleRight}px;` : '')
|
||||||
|
|
||||||
|
function buildSlots(total: number): Array<number> {
|
||||||
|
const slots: Array<number> = []
|
||||||
|
for (let i = 0; i < total; i++) {
|
||||||
|
slots.push(i)
|
||||||
|
}
|
||||||
|
return slots
|
||||||
|
}
|
||||||
|
|
||||||
|
const categorySlots = computed((): Array<number> => buildSlots(props.categoryCount))
|
||||||
|
const channelSlots = computed((): Array<number> => buildSlots(props.channelCount))
|
||||||
|
const productSlots = computed((): Array<number> => buildSlots(props.productCount))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.home-skeleton-root {
|
||||||
|
min-height: 100%;
|
||||||
|
background: linear-gradient(180deg, #f6f8fb 0%, #f9fafc 42%, #f4f6f8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-header {
|
||||||
|
padding-left: 24rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
background: linear-gradient(180deg, #f6f8fb 0%, rgba(246, 248, 251, 0.98) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-tab-row {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
height: 62rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 6rpx;
|
||||||
|
padding-bottom: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-tab {
|
||||||
|
min-width: 132rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #eceff3;
|
||||||
|
border-width: 2rpx;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-tab-active {
|
||||||
|
border-color: rgba(215, 39, 39, 0.16);
|
||||||
|
background-color: #fef2f2;
|
||||||
|
box-shadow: 0 10rpx 22rpx rgba(215, 39, 39, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-search {
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
box-shadow: 0 10rpx 24rpx rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-search-icon {
|
||||||
|
width: 26rpx;
|
||||||
|
height: 26rpx;
|
||||||
|
border-radius: 13rpx;
|
||||||
|
background-color: rgba(176, 184, 196, 0.55);
|
||||||
|
margin-right: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-search-line {
|
||||||
|
height: 24rpx;
|
||||||
|
width: 320rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(176, 184, 196, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-category-scroll {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-category-row {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
gap: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-category-pill {
|
||||||
|
width: 112rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-category-pill-active {
|
||||||
|
width: 128rpx;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-body {
|
||||||
|
padding: 20rpx 24rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-banner {
|
||||||
|
height: 180rpx;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
margin-bottom: 22rpx;
|
||||||
|
box-shadow: 0 14rpx 28rpx rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-channel-grid {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
row-gap: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-channel-card {
|
||||||
|
width: 164rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 10rpx 22rpx rgba(15, 23, 42, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-channel-icon {
|
||||||
|
width: 76rpx;
|
||||||
|
height: 76rpx;
|
||||||
|
border-radius: 38rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
margin-bottom: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-channel-line {
|
||||||
|
width: 94rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-channel-line-short {
|
||||||
|
width: 66rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-feed-header {
|
||||||
|
margin-bottom: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-feed-title {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 14rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-feed-subtitle {
|
||||||
|
width: 240rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-grid {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
row-gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-card {
|
||||||
|
width: 340rpx;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 12rpx 26rpx rgba(15, 23, 42, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-image {
|
||||||
|
height: 340rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-body {
|
||||||
|
padding: 18rpx 18rpx 22rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-line {
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
margin-bottom: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-line-short {
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-price-row {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-price {
|
||||||
|
width: 128rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 14rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-product-origin {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-safe-bottom {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-shimmer {
|
||||||
|
background: linear-gradient(90deg, #edf0f4 0%, #f9fbfd 48%, #edf0f4 100%);
|
||||||
|
background-size: 220% 100%;
|
||||||
|
animation: skeleton-shimmer 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -20% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -100,9 +100,11 @@
|
|||||||
@click="emit('select-product', product)"
|
@click="emit('select-product', product)"
|
||||||
>
|
>
|
||||||
<view class="hmall-product-image-wrapper hmall-product-image-wrapper-fixed">
|
<view class="hmall-product-image-wrapper hmall-product-image-wrapper-fixed">
|
||||||
|
<view class="hmall-product-image-placeholder"></view>
|
||||||
<image
|
<image
|
||||||
class="hmall-product-image"
|
:class="['hmall-product-image', isProductImageLoaded(product.id) ? 'hmall-product-image-loaded' : '']"
|
||||||
:src="getProductCover(product)"
|
:src="getProductCover(product)"
|
||||||
|
@load="() => handleProductImageLoad(product.id)"
|
||||||
@error="() => handleProductImageError(product.id)"
|
@error="() => handleProductImageError(product.id)"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
/>
|
/>
|
||||||
@@ -137,11 +139,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="loading" class="hmall-loading-state">
|
<view v-else-if="pageLoading || loading" class="hmall-loading-state">
|
||||||
<text class="hmall-loading-text">正在加载商品...</text>
|
<text class="hmall-loading-text">正在加载商品...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="hmall-empty-wrap">
|
<view v-else-if="!pageLoading" class="hmall-empty-wrap">
|
||||||
<view v-if="emptyStateDescription != ''" class="hmall-empty-state-extended">
|
<view v-if="emptyStateDescription != ''" class="hmall-empty-state-extended">
|
||||||
<EmptyState :text="emptyStateTitle"></EmptyState>
|
<EmptyState :text="emptyStateTitle"></EmptyState>
|
||||||
<text class="hmall-empty-desc">{{ emptyStateDescription }}</text>
|
<text class="hmall-empty-desc">{{ emptyStateDescription }}</text>
|
||||||
@@ -168,6 +170,7 @@ import type { MarketingChannel, ChannelProduct, SimpleCategoryChannel } from '@/
|
|||||||
|
|
||||||
const failedProductImageIds = ref<string[]>([])
|
const failedProductImageIds = ref<string[]>([])
|
||||||
const failedChannelImageIds = ref<string[]>([])
|
const failedChannelImageIds = ref<string[]>([])
|
||||||
|
const loadedProductImageIds = ref<string[]>([])
|
||||||
|
|
||||||
type SecondaryCategoryPage = {
|
type SecondaryCategoryPage = {
|
||||||
id: string
|
id: string
|
||||||
@@ -199,6 +202,10 @@ const props = defineProps({
|
|||||||
type: Array<Product>,
|
type: Array<Product>,
|
||||||
default: [] as Array<Product>
|
default: [] as Array<Product>
|
||||||
},
|
},
|
||||||
|
pageLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
@@ -339,11 +346,28 @@ function handleProductImageError(productId: string): void {
|
|||||||
if (productId == '') {
|
if (productId == '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
handleProductImageLoad(productId)
|
||||||
if (failedProductImageIds.value.indexOf(productId) == -1) {
|
if (failedProductImageIds.value.indexOf(productId) == -1) {
|
||||||
failedProductImageIds.value.push(productId)
|
failedProductImageIds.value.push(productId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleProductImageLoad(productId: string): void {
|
||||||
|
if (productId == '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (loadedProductImageIds.value.indexOf(productId) == -1) {
|
||||||
|
loadedProductImageIds.value.push(productId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isProductImageLoaded(productId: string): boolean {
|
||||||
|
if (productId == '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return loadedProductImageIds.value.indexOf(productId) != -1
|
||||||
|
}
|
||||||
|
|
||||||
function getProductTitle(product: Product): string {
|
function getProductTitle(product: Product): string {
|
||||||
if (product.short_title != null && product.short_title != '') {
|
if (product.short_title != null && product.short_title != '') {
|
||||||
return product.short_title
|
return product.short_title
|
||||||
@@ -777,6 +801,17 @@ function showMarketPrice(product: Product): boolean {
|
|||||||
background: #f2f2f2;
|
background: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hmall-product-image-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(90deg, #edf0f4 0%, #f9fbfd 48%, #edf0f4 100%);
|
||||||
|
background-size: 220% 100%;
|
||||||
|
animation: hmall-image-shimmer 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.hmall-product-image {
|
.hmall-product-image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -784,6 +819,12 @@ function showMarketPrice(product: Product): boolean {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 18rpx 18rpx 0 0;
|
border-radius: 18rpx 18rpx 0 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 220ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hmall-product-image-loaded {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hmall-product-image-wrapper-fixed {
|
.hmall-product-image-wrapper-fixed {
|
||||||
@@ -795,6 +836,15 @@ function showMarketPrice(product: Product): boolean {
|
|||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes hmall-image-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -20% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hmall-product-body {
|
.hmall-product-body {
|
||||||
padding: 14rpx 14rpx 16rpx;
|
padding: 14rpx 14rpx 16rpx;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
<view
|
<view
|
||||||
v-for="item in modules"
|
v-for="item in modules"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
class="jd2-module-item"
|
:class="['jd2-module-item', activeModule == item.key ? 'jd2-module-item-active' : '', isServiceModule ? 'jd2-module-item-service' : '']"
|
||||||
:class="isServiceModule ? 'jd2-module-item-service' : ''"
|
|
||||||
@click="emit('change-module', item.key)"
|
@click="emit('change-module', item.key)"
|
||||||
>
|
>
|
||||||
<text :class="['jd2-module-text', activeModule == item.key ? 'jd2-module-text-active' : '', isServiceModule ? 'jd2-module-text-service' : '']">{{ item.label }}</text>
|
<text :class="['jd2-module-text', activeModule == item.key ? 'jd2-module-text-active' : '', isServiceModule ? 'jd2-module-text-service' : '']">{{ item.label }}</text>
|
||||||
<view v-if="activeModule == item.key" :class="['jd2-module-line', isServiceModule ? 'jd2-module-line-service' : '']"></view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -121,17 +119,27 @@ function handleSearch() {
|
|||||||
padding-right: 24rpx;
|
padding-right: 24rpx;
|
||||||
height: 62rpx;
|
height: 62rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
padding-top: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jd2-module-item {
|
.jd2-module-item {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 62rpx;
|
min-width: 132rpx;
|
||||||
margin-right: 36rpx;
|
height: 56rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #eceff3;
|
||||||
|
border-width: 2rpx;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jd2-module-item-service {
|
.jd2-module-item-service {
|
||||||
margin-right: 28rpx;
|
margin-right: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jd2-module-text {
|
.jd2-module-text {
|
||||||
@@ -155,16 +163,18 @@ function handleSearch() {
|
|||||||
color: #0f766e;
|
color: #0f766e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jd2-module-item-active {
|
||||||
|
border-color: rgba(215, 39, 39, 0.16);
|
||||||
|
background-color: #fef2f2;
|
||||||
|
box-shadow: 0 10rpx 22rpx rgba(215, 39, 39, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.jd2-module-line {
|
.jd2-module-line {
|
||||||
margin-top: 8rpx;
|
display: none;
|
||||||
width: 32rpx;
|
|
||||||
height: 6rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
background: #e2231a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.jd2-module-line-service {
|
.jd2-module-line-service {
|
||||||
background: #16a085;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jd2-search-wrap {
|
.jd2-search-wrap {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ ALTER TABLE public.ml_delivery_stations ENABLE ROW LEVEL SECURITY;
|
|||||||
|
|
||||||
-- 清理旧策略
|
-- 清理旧策略
|
||||||
DROP POLICY IF EXISTS delivery_staff_self_select ON public.ml_delivery_staff;
|
DROP POLICY IF EXISTS delivery_staff_self_select ON public.ml_delivery_staff;
|
||||||
|
DROP POLICY IF EXISTS delivery_staff_self_update ON public.ml_delivery_staff;
|
||||||
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
|
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
|
||||||
|
|
||||||
-- 1. 执行人员本人可直读自己的未删除档案
|
-- 1. 执行人员本人可直读自己的未删除档案
|
||||||
@@ -28,11 +29,35 @@ CREATE POLICY delivery_staff_self_select
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 2. 提货点/机构对前台保持只读,仅返回启用且未删除数据
|
-- 2. 执行人员本人可更新自己的在线状态等自有档案字段
|
||||||
|
CREATE POLICY delivery_staff_self_update
|
||||||
|
ON public.ml_delivery_staff
|
||||||
|
FOR UPDATE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
deleted_at IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM public.ak_users u
|
||||||
|
WHERE u.id = ml_delivery_staff.uid
|
||||||
|
AND u.auth_id = auth.uid()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WITH CHECK (
|
||||||
|
deleted_at IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM public.ak_users u
|
||||||
|
WHERE u.id = ml_delivery_staff.uid
|
||||||
|
AND u.auth_id = auth.uid()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 3. 提货点/机构对前台保持只读,仅返回启用且未删除数据
|
||||||
CREATE POLICY delivery_stations_select_active
|
CREATE POLICY delivery_stations_select_active
|
||||||
ON public.ml_delivery_stations
|
ON public.ml_delivery_stations
|
||||||
FOR SELECT
|
FOR SELECT
|
||||||
TO anon, authenticated
|
TO anon, authenticated
|
||||||
USING (status = 1 AND deleted_at IS NULL);
|
USING (status = 1 AND deleted_at IS NULL);
|
||||||
|
|
||||||
-- 3. 其余直连写操作默认不开放,管理端统一走 SECURITY DEFINER RPC
|
-- 4. 其余直连写操作默认不开放,管理端统一走 SECURITY DEFINER RPC
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ ALTER TABLE public.ml_delivery_staff ENABLE ROW LEVEL SECURITY;
|
|||||||
ALTER TABLE public.ml_delivery_stations ENABLE ROW LEVEL SECURITY;
|
ALTER TABLE public.ml_delivery_stations ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
DROP POLICY IF EXISTS delivery_staff_self_select ON public.ml_delivery_staff;
|
DROP POLICY IF EXISTS delivery_staff_self_select ON public.ml_delivery_staff;
|
||||||
|
DROP POLICY IF EXISTS delivery_staff_self_update ON public.ml_delivery_staff;
|
||||||
CREATE POLICY delivery_staff_self_select
|
CREATE POLICY delivery_staff_self_select
|
||||||
ON public.ml_delivery_staff
|
ON public.ml_delivery_staff
|
||||||
FOR SELECT
|
FOR SELECT
|
||||||
@@ -93,6 +94,29 @@ CREATE POLICY delivery_staff_self_select
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE POLICY delivery_staff_self_update
|
||||||
|
ON public.ml_delivery_staff
|
||||||
|
FOR UPDATE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
deleted_at IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM public.ak_users u
|
||||||
|
WHERE u.id = ml_delivery_staff.uid
|
||||||
|
AND u.auth_id = auth.uid()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WITH CHECK (
|
||||||
|
deleted_at IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM public.ak_users u
|
||||||
|
WHERE u.id = ml_delivery_staff.uid
|
||||||
|
AND u.auth_id = auth.uid()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
|
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
|
||||||
CREATE POLICY delivery_stations_select_active
|
CREATE POLICY delivery_stations_select_active
|
||||||
ON public.ml_delivery_stations
|
ON public.ml_delivery_stations
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
begin;
|
||||||
|
|
||||||
|
alter table if exists public.ak_users
|
||||||
|
drop constraint if exists ak_users_preferred_language_fkey;
|
||||||
|
|
||||||
|
alter table if exists public.ak_users
|
||||||
|
add constraint ak_users_preferred_language_fkey
|
||||||
|
foreign key (preferred_language)
|
||||||
|
references public.ak_languages (id)
|
||||||
|
on update cascade
|
||||||
|
on delete set null;
|
||||||
|
|
||||||
|
create index if not exists idx_ak_users_preferred_language
|
||||||
|
on public.ak_users (preferred_language);
|
||||||
|
|
||||||
|
alter table if exists public.ak_languages enable row level security;
|
||||||
|
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
if exists (
|
||||||
|
select 1
|
||||||
|
from pg_class c
|
||||||
|
join pg_namespace n on n.oid = c.relnamespace
|
||||||
|
where n.nspname = 'public'
|
||||||
|
and c.relname = 'ak_languages'
|
||||||
|
and c.relkind = 'r'
|
||||||
|
) and not exists (
|
||||||
|
select 1
|
||||||
|
from pg_policies
|
||||||
|
where schemaname = 'public'
|
||||||
|
and tablename = 'ak_languages'
|
||||||
|
and policyname = 'ak_languages_active_select'
|
||||||
|
) then
|
||||||
|
create policy ak_languages_active_select
|
||||||
|
on public.ak_languages
|
||||||
|
for select
|
||||||
|
using (is_active = true);
|
||||||
|
end if;
|
||||||
|
end
|
||||||
|
$$;
|
||||||
|
|
||||||
|
commit;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<!-- pages/main/index.uvue -->
|
<!-- pages/main/index.uvue -->
|
||||||
<template>
|
<template>
|
||||||
<view class="medic-home">
|
<view class="medic-home">
|
||||||
|
<view :class="['home-main-stage', pageLoading ? 'home-main-stage-hidden' : 'home-main-stage-visible']">
|
||||||
<view class="jd-header-fixed">
|
<view class="jd-header-fixed">
|
||||||
<JdLikeHomeHeader
|
<JdLikeHomeHeader
|
||||||
:status-bar-height="statusBarHeight"
|
:status-bar-height="statusBarHeight"
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
<HomeMallContent
|
<HomeMallContent
|
||||||
v-if="activeTopModule == 'home'"
|
v-if="activeTopModule == 'home'"
|
||||||
:current-category="currentCategory"
|
:current-category="currentCategory"
|
||||||
|
:page-loading="pageLoading"
|
||||||
:selected-sub-category-id="selectedSubCategoryId"
|
:selected-sub-category-id="selectedSubCategoryId"
|
||||||
:secondary-category-display="secondaryCategoryDisplay"
|
:secondary-category-display="secondaryCategoryDisplay"
|
||||||
:marketing-channels="marketingChannels"
|
:marketing-channels="marketingChannels"
|
||||||
@@ -221,6 +223,22 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="showHomeSkeleton"
|
||||||
|
:class="['home-skeleton-overlay', pageLoading ? 'home-skeleton-overlay-visible' : 'home-skeleton-overlay-leaving']"
|
||||||
|
>
|
||||||
|
<HomeSkeleton
|
||||||
|
:status-bar-height="statusBarHeight"
|
||||||
|
:capsule-right="navBarRight"
|
||||||
|
:bottom-safe-area="bottomSafeArea"
|
||||||
|
:show-category-bar="true"
|
||||||
|
:category-count="7"
|
||||||
|
:channel-count="4"
|
||||||
|
:product-count="8"
|
||||||
|
></HomeSkeleton>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
@@ -228,6 +246,7 @@ import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
|||||||
import { onShow, onLoad, onHide } from '@dcloudio/uni-app'
|
import { onShow, onLoad, onHide } from '@dcloudio/uni-app'
|
||||||
import HomeMallContent from '@/components/home/HomeMallContent.uvue'
|
import HomeMallContent from '@/components/home/HomeMallContent.uvue'
|
||||||
import JdLikeHomeHeader from '@/components/home/JdLikeHomeHeader.uvue'
|
import JdLikeHomeHeader from '@/components/home/JdLikeHomeHeader.uvue'
|
||||||
|
import HomeSkeleton from '@/components/home-skeleton/home-skeleton.uvue'
|
||||||
import { fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
import { fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||||
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||||
import supabaseService from '@/utils/supabaseService.uts'
|
import supabaseService from '@/utils/supabaseService.uts'
|
||||||
@@ -246,6 +265,11 @@ const refreshing = ref(false)
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const isFirstShow = ref(true)
|
const isFirstShow = ref(true)
|
||||||
const hasMore = ref(true)
|
const hasMore = ref(true)
|
||||||
|
const pageLoading = ref(true)
|
||||||
|
const goodsLoaded = ref(false)
|
||||||
|
const categoryLoaded = ref(false)
|
||||||
|
const bannerLoaded = ref(false)
|
||||||
|
const skeletonExiting = ref(false)
|
||||||
const activeSort = ref('recommend') // 默认展示智能推荐
|
const activeSort = ref('recommend') // 默认展示智能推荐
|
||||||
const activeFilter = ref('recommend')
|
const activeFilter = ref('recommend')
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
@@ -598,6 +622,11 @@ const nextPlaceholderKeyword = ref(placeholderKeywords.value.length > 1 ? placeh
|
|||||||
const placeholderAnimating = ref(false)
|
const placeholderAnimating = ref(false)
|
||||||
let placeholderTimer: number = 0
|
let placeholderTimer: number = 0
|
||||||
let placeholderAnimationTimer: number = 0
|
let placeholderAnimationTimer: number = 0
|
||||||
|
let skeletonTimer: number = 0
|
||||||
|
|
||||||
|
const showHomeSkeleton = computed((): boolean => {
|
||||||
|
return pageLoading.value || skeletonExiting.value
|
||||||
|
})
|
||||||
|
|
||||||
const homeEmptyStateTitle = computed((): string => {
|
const homeEmptyStateTitle = computed((): string => {
|
||||||
if (currentFeedCategoryId.value == 'recommend') {
|
if (currentFeedCategoryId.value == 'recommend') {
|
||||||
@@ -660,6 +689,42 @@ function stopPlaceholderScroll(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetInitialHomeLoadFlags(): void {
|
||||||
|
goodsLoaded.value = false
|
||||||
|
categoryLoaded.value = false
|
||||||
|
bannerLoaded.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSkeletonTimer(): void {
|
||||||
|
if (skeletonTimer != 0) {
|
||||||
|
clearTimeout(skeletonTimer)
|
||||||
|
skeletonTimer = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginHomeSkeleton(): void {
|
||||||
|
clearSkeletonTimer()
|
||||||
|
pageLoading.value = true
|
||||||
|
skeletonExiting.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishHomeSkeleton(): void {
|
||||||
|
if (!pageLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pageLoading.value = false
|
||||||
|
skeletonExiting.value = true
|
||||||
|
clearSkeletonTimer()
|
||||||
|
skeletonTimer = setTimeout(() => {
|
||||||
|
skeletonExiting.value = false
|
||||||
|
skeletonTimer = 0
|
||||||
|
}, 280)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadHomeBannerData(): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
function startPlaceholderScroll(): void {
|
function startPlaceholderScroll(): void {
|
||||||
try {
|
try {
|
||||||
if (placeholderTimer != 0) {
|
if (placeholderTimer != 0) {
|
||||||
@@ -1910,9 +1975,17 @@ const searchByKeyword = (keyword: string): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
const initData = async () => {
|
type InitDataOptions = {
|
||||||
|
showSkeleton: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const initData = async (options: InitDataOptions = { showSkeleton: false }) => {
|
||||||
logSupaConfig()
|
logSupaConfig()
|
||||||
console.log('[consumer-db] 首页开始加载数据')
|
console.log('[consumer-db] 首页开始加载数据')
|
||||||
|
resetInitialHomeLoadFlags()
|
||||||
|
if (options.showSkeleton) {
|
||||||
|
beginHomeSkeleton()
|
||||||
|
}
|
||||||
// 首先确保用户资料已加载
|
// 首先确保用户资料已加载
|
||||||
try {
|
try {
|
||||||
await getCurrentUser()
|
await getCurrentUser()
|
||||||
@@ -1920,16 +1993,50 @@ const initData = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载用户资料失败:', error)
|
console.error('加载用户资料失败:', error)
|
||||||
}
|
}
|
||||||
|
const categoryTask = (async (): Promise<void> => {
|
||||||
|
try {
|
||||||
await loadMedicalMallCategories()
|
await loadMedicalMallCategories()
|
||||||
await loadBrands()
|
} finally {
|
||||||
await loadHotKeywords()
|
categoryLoaded.value = true
|
||||||
await loadServiceHomeData()
|
|
||||||
if (await consumeSelectedCategoryFromStorage()) {
|
|
||||||
await loadRecommendedProducts(defaultLoadLimit)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
})()
|
||||||
|
const bannerTask = (async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await loadHomeBannerData()
|
||||||
|
} finally {
|
||||||
|
bannerLoaded.value = true
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
const goodsTask = (async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await categoryTask
|
||||||
|
if (!(await consumeSelectedCategoryFromStorage())) {
|
||||||
await loadCategoryGoods(currentCategory.value)
|
await loadCategoryGoods(currentCategory.value)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
goodsLoaded.value = true
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
const supplementTask = (async (): Promise<void> => {
|
||||||
|
await Promise.all([
|
||||||
|
loadBrands(),
|
||||||
|
loadHotKeywords(),
|
||||||
|
loadServiceHomeData()
|
||||||
|
])
|
||||||
|
try {
|
||||||
await loadRecommendedProducts(defaultLoadLimit)
|
await loadRecommendedProducts(defaultLoadLimit)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载推荐频道商品失败:', error)
|
||||||
|
recommendedProducts.value = []
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
try {
|
||||||
|
await Promise.all([categoryTask, goodsTask, bannerTask, supplementTask])
|
||||||
|
} finally {
|
||||||
|
if (options.showSkeleton && categoryLoaded.value && goodsLoaded.value && bannerLoaded.value) {
|
||||||
|
finishHomeSkeleton()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2035,7 +2142,7 @@ const initPage = () => {
|
|||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initPage()
|
initPage()
|
||||||
initData()
|
void initData({ showSkeleton: true })
|
||||||
startPlaceholderScroll()
|
startPlaceholderScroll()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2085,6 +2192,7 @@ onHide(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopPlaceholderScroll()
|
stopPlaceholderScroll()
|
||||||
|
clearSkeletonTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 处理滚动事件
|
// 处理滚动事件
|
||||||
@@ -2202,7 +2310,7 @@ const onRefresh = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 重新加载数据
|
// 重新加载数据
|
||||||
await initData()
|
await initData({ showSkeleton: false })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('刷新数据失败:', e)
|
console.error('刷新数据失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2432,6 +2540,41 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-main-stage {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
transition: opacity 260ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-main-stage-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-main-stage-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1200;
|
||||||
|
background: #f6f8fb;
|
||||||
|
transition: opacity 260ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-overlay-visible {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-skeleton-overlay-leaving {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.jd-header-fixed {
|
.jd-header-fixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
<view class="info-row last-row" @click="openLanguagePicker">
|
<view class="info-row last-row" @click="openLanguagePicker">
|
||||||
<text class="info-label">语言偏好</text>
|
<text class="info-label">语言偏好</text>
|
||||||
<view class="info-value-wrap">
|
<view class="info-value-wrap">
|
||||||
<text class="info-value">{{ getLanguageText(profile.preferred_language) }}</text>
|
<text class="info-value">{{ getPreferredLanguageText() }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="info-arrow">›</text>
|
<text class="info-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -225,8 +225,8 @@
|
|||||||
<view v-if="showLanguagePicker" class="picker-modal">
|
<view v-if="showLanguagePicker" class="picker-modal">
|
||||||
<picker-view class="picker-view" :value="tempLanguageIndex" :indicator-style="'height: 50px;'" @change="onLanguagePickerViewChange">
|
<picker-view class="picker-view" :value="tempLanguageIndex" :indicator-style="'height: 50px;'" @change="onLanguagePickerViewChange">
|
||||||
<picker-view-column style="width:750rpx;">
|
<picker-view-column style="width:750rpx;">
|
||||||
<view v-for="(language, idx) in languageOptions" :key="language" class="picker-item">
|
<view v-for="(language, idx) in languageOptions" :key="language.id" class="picker-item">
|
||||||
{{ getLanguageText(language) }}
|
{{ language.label }}
|
||||||
</view>
|
</view>
|
||||||
</picker-view-column>
|
</picker-view-column>
|
||||||
</picker-view>
|
</picker-view>
|
||||||
@@ -317,19 +317,41 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import supa from '@/components/supadb/aksupainstance.uts'
|
import supa from '@/components/supadb/aksupainstance.uts'
|
||||||
import { AkSupaSelectOptions } from '@/components/supadb/aksupa.uts'
|
|
||||||
import { setUserProfile } from '@/utils/store.uts'
|
import { setUserProfile } from '@/utils/store.uts'
|
||||||
import { PROFILE_REGION_OPTIONS, PROFILE_COMMON_ADDRESS_SUGGESTIONS } from '@/utils/profileRegionData.uts'
|
import { PROFILE_REGION_OPTIONS, PROFILE_COMMON_ADDRESS_SUGGESTIONS } from '@/utils/profileRegionData.uts'
|
||||||
import type { UserProfile } from '@/types/mall-types.uts'
|
import type { UserProfile } from '@/types/mall-types.uts'
|
||||||
|
|
||||||
|
type AkLanguageRow = {
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
native_name: string
|
||||||
|
is_active: boolean
|
||||||
|
is_default: boolean
|
||||||
|
sort_order: number
|
||||||
|
created_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LanguageOption = {
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
nativeName: string
|
||||||
|
label: string
|
||||||
|
isDefault: boolean
|
||||||
|
sortOrder: number
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
|
||||||
const isLoading = ref<boolean>(false)
|
const isLoading = ref<boolean>(false)
|
||||||
const isSaving = ref<boolean>(false)
|
const isSaving = ref<boolean>(false)
|
||||||
const userAvatar = ref<string>('/static/logo.png')
|
const userAvatar = ref<string>('/static/logo.png')
|
||||||
const hasLoadError = ref<boolean>(false)
|
const hasLoadError = ref<boolean>(false)
|
||||||
const registeredDate = ref<string>('暂未记录')
|
const registeredDate = ref<string>('暂未记录')
|
||||||
const profileRowId = ref<string>('')
|
const profileRowId = ref<string>('')
|
||||||
|
const originalProfile = ref<UserProfile | null>(null)
|
||||||
const genderOptions: Array<string> = ['male', 'female', 'other']
|
const genderOptions: Array<string> = ['male', 'female', 'other']
|
||||||
const languageOptions: Array<string> = ['zh-CN', 'en-US']
|
const languageOptions = ref<Array<LanguageOption>>([])
|
||||||
const customOptionLabel = '自定义其他'
|
const customOptionLabel = '自定义其他'
|
||||||
const healthGoalOptions: Array<string> = ['改善睡眠', '康复护理', '日常保健', '慢病管理', '营养调理', customOptionLabel]
|
const healthGoalOptions: Array<string> = ['改善睡眠', '康复护理', '日常保健', '慢病管理', '营养调理', customOptionLabel]
|
||||||
const carePreferenceOptions: Array<string> = ['上门护理', '陪诊陪护', '康复训练', '定期回访', '电话提醒', customOptionLabel]
|
const carePreferenceOptions: Array<string> = ['上门护理', '陪诊陪护', '康复训练', '定期回访', '电话提醒', customOptionLabel]
|
||||||
@@ -419,7 +441,7 @@ const profile = ref<UserProfile>({
|
|||||||
weight_kg: 0,
|
weight_kg: 0,
|
||||||
bio: '',
|
bio: '',
|
||||||
avatar_url: '/static/logo.png',
|
avatar_url: '/static/logo.png',
|
||||||
preferred_language: 'zh-CN',
|
preferred_language: '',
|
||||||
health_goal: '',
|
health_goal: '',
|
||||||
service_address: '',
|
service_address: '',
|
||||||
emergency_contact: '',
|
emergency_contact: '',
|
||||||
@@ -536,11 +558,167 @@ const getBmiText = (): string => {
|
|||||||
return '' + rounded
|
return '' + rounded
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLanguageText = (languageCode: string | null): string => {
|
const normalizeString = (value: string | null | undefined): string | null => {
|
||||||
if (languageCode == 'en-US') {
|
if (value == null) {
|
||||||
return 'English'
|
return null
|
||||||
}
|
}
|
||||||
return '中文'
|
const trimmed = value.trim()
|
||||||
|
return trimmed == '' ? null : trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeNumber = (value: number | null | undefined): number | null => {
|
||||||
|
if (value == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const numericValue = Number(value)
|
||||||
|
return numericValue == numericValue ? numericValue : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeDateString = (value: string | null | undefined): string | null => {
|
||||||
|
const normalized = normalizeString(value)
|
||||||
|
if (normalized == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const parts = normalized.split('T')
|
||||||
|
return parts.length > 0 ? parts[0] : normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
const deepCloneProfile = (source: UserProfile): UserProfile => {
|
||||||
|
return {
|
||||||
|
id: source.id ?? '',
|
||||||
|
username: source.username ?? '',
|
||||||
|
email: source.email ?? '',
|
||||||
|
gender: source.gender ?? '',
|
||||||
|
birthday: source.birthday ?? '',
|
||||||
|
height_cm: source.height_cm ?? 0,
|
||||||
|
weight_kg: source.weight_kg ?? 0,
|
||||||
|
bio: source.bio ?? '',
|
||||||
|
avatar_url: source.avatar_url ?? '',
|
||||||
|
preferred_language: source.preferred_language ?? '',
|
||||||
|
health_goal: source.health_goal ?? '',
|
||||||
|
service_address: source.service_address ?? '',
|
||||||
|
emergency_contact: source.emergency_contact ?? '',
|
||||||
|
chronic_notes: source.chronic_notes ?? '',
|
||||||
|
care_preference: source.care_preference ?? '',
|
||||||
|
role: source.role ?? '',
|
||||||
|
school_id: source.school_id ?? '',
|
||||||
|
grade_id: source.grade_id ?? '',
|
||||||
|
class_id: source.class_id ?? '',
|
||||||
|
created_at: source.created_at ?? '',
|
||||||
|
updated_at: source.updated_at ?? ''
|
||||||
|
} as UserProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLanguageOptionById = (languageId: string | null | undefined): LanguageOption | null => {
|
||||||
|
const normalizedId = normalizeString(languageId)
|
||||||
|
if (normalizedId == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||||
|
const option = languageOptions.value[i]
|
||||||
|
if (option.id == normalizedId) {
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultLanguageOption = (): LanguageOption | null => {
|
||||||
|
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||||
|
if (languageOptions.value[i].isDefault == true) {
|
||||||
|
return languageOptions.value[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||||
|
if (languageOptions.value[i].code == 'zh-CN') {
|
||||||
|
return languageOptions.value[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return languageOptions.value.length > 0 ? languageOptions.value[0] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLanguageLabel = (languageId: string | null): string => {
|
||||||
|
const language = getLanguageOptionById(languageId)
|
||||||
|
return language != null ? language.label : '未设置'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPreferredLanguageText = (): string => {
|
||||||
|
const currentLanguageId = normalizeString(profile.value.preferred_language)
|
||||||
|
if (currentLanguageId != null) {
|
||||||
|
return getLanguageLabel(currentLanguageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLanguage = getDefaultLanguageOption()
|
||||||
|
return defaultLanguage != null ? defaultLanguage.label : '未设置'
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadLanguageOptions = async (): Promise<void> => {
|
||||||
|
const result = await supa
|
||||||
|
.from('ak_languages')
|
||||||
|
.select('id, code, name, native_name, is_active, is_default, sort_order, created_at', {} as UTSJSONObject)
|
||||||
|
.eq('is_active', true)
|
||||||
|
.order('sort_order', { ascending: true })
|
||||||
|
.execute()
|
||||||
|
|
||||||
|
if (result.error != null) {
|
||||||
|
console.error('加载语言列表失败:', JSON.stringify(result.error))
|
||||||
|
languageOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = result.data
|
||||||
|
const options: Array<LanguageOption> = []
|
||||||
|
if (Array.isArray(rows)) {
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const item = rows[i] as UTSJSONObject
|
||||||
|
const row: AkLanguageRow = {
|
||||||
|
id: item.getString('id') ?? '',
|
||||||
|
code: item.getString('code') ?? '',
|
||||||
|
name: item.getString('name') ?? '',
|
||||||
|
native_name: item.getString('native_name') ?? '',
|
||||||
|
is_active: item.getBoolean('is_active') ?? false,
|
||||||
|
is_default: item.getBoolean('is_default') ?? false,
|
||||||
|
sort_order: item.getNumber('sort_order') ?? 0,
|
||||||
|
created_at: item.getString('created_at') ?? ''
|
||||||
|
}
|
||||||
|
if (row.id == '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const label = row.native_name != '' ? row.native_name : (row.name != '' ? row.name : row.code)
|
||||||
|
options.push({
|
||||||
|
id: row.id,
|
||||||
|
code: row.code,
|
||||||
|
name: row.name,
|
||||||
|
nativeName: row.native_name,
|
||||||
|
label,
|
||||||
|
isDefault: row.is_default,
|
||||||
|
sortOrder: row.sort_order,
|
||||||
|
createdAt: row.created_at ?? ''
|
||||||
|
} as LanguageOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < options.length; i++) {
|
||||||
|
for (let j = i + 1; j < options.length; j++) {
|
||||||
|
let shouldSwap = false
|
||||||
|
if (options[j].sortOrder < options[i].sortOrder) {
|
||||||
|
shouldSwap = true
|
||||||
|
} else if (options[j].sortOrder == options[i].sortOrder && options[j].createdAt != '' && options[i].createdAt != '' && options[j].createdAt < options[i].createdAt) {
|
||||||
|
shouldSwap = true
|
||||||
|
}
|
||||||
|
if (shouldSwap) {
|
||||||
|
const temp = options[i]
|
||||||
|
options[i] = options[j]
|
||||||
|
options[j] = temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
languageOptions.value = options
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTextOrPlaceholder = (fieldValue: string | null | undefined, placeholder: string): string => {
|
const getTextOrPlaceholder = (fieldValue: string | null | undefined, placeholder: string): string => {
|
||||||
@@ -878,19 +1056,37 @@ const confirmAddressPicker = (): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openLanguagePicker = (): void => {
|
const openLanguagePicker = (): void => {
|
||||||
const languageValue = profile.value.preferred_language
|
if (languageOptions.value.length == 0) {
|
||||||
const idx = languageValue != null ? languageOptions.indexOf(languageValue) : -1
|
uni.showToast({
|
||||||
tempLanguageIndex.value = [idx >= 0 ? idx : 0]
|
title: '语言列表加载中',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLanguageId = normalizeString(profile.value.preferred_language)
|
||||||
|
const defaultLanguage = getDefaultLanguageOption()
|
||||||
|
const targetLanguageId = currentLanguageId != null ? currentLanguageId : (defaultLanguage != null ? defaultLanguage.id : '')
|
||||||
|
let idx = 0
|
||||||
|
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||||
|
if (languageOptions.value[i].id == targetLanguageId) {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempLanguageIndex.value = [idx]
|
||||||
showLanguagePicker.value = true
|
showLanguagePicker.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLanguagePickerViewChange = (e: UniPickerViewChangeEvent): void => {
|
const onLanguagePickerViewChange = (e: UniPickerViewChangeEvent): void => {
|
||||||
const idx = e.detail.value[0]
|
const idx = e.detail.value[0]
|
||||||
tempLanguageIndex.value = [(idx >= 0 && idx < languageOptions.length) ? idx : 0]
|
tempLanguageIndex.value = [(idx >= 0 && idx < languageOptions.value.length) ? idx : 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmLanguagePicker = (): void => {
|
const confirmLanguagePicker = (): void => {
|
||||||
profile.value.preferred_language = languageOptions[tempLanguageIndex.value[0]]
|
if (languageOptions.value.length > 0) {
|
||||||
|
profile.value.preferred_language = languageOptions.value[tempLanguageIndex.value[0]].id
|
||||||
|
}
|
||||||
showLanguagePicker.value = false
|
showLanguagePicker.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,7 +1134,7 @@ const loadProfile = async (): Promise<void> => {
|
|||||||
weight_kg: prodata.getNumber('weight_kg') ?? 0,
|
weight_kg: prodata.getNumber('weight_kg') ?? 0,
|
||||||
bio: prodata.getString('bio') ?? '',
|
bio: prodata.getString('bio') ?? '',
|
||||||
avatar_url: prodata.getString('avatar_url') ?? '/static/logo.png',
|
avatar_url: prodata.getString('avatar_url') ?? '/static/logo.png',
|
||||||
preferred_language: prodata.getString('preferred_language') ?? 'zh-CN',
|
preferred_language: prodata.getString('preferred_language') ?? '',
|
||||||
health_goal: prodata.getString('health_goal') ?? '',
|
health_goal: prodata.getString('health_goal') ?? '',
|
||||||
service_address: prodata.getString('service_address') ?? '',
|
service_address: prodata.getString('service_address') ?? '',
|
||||||
emergency_contact: prodata.getString('emergency_contact') ?? '',
|
emergency_contact: prodata.getString('emergency_contact') ?? '',
|
||||||
@@ -947,6 +1143,7 @@ const loadProfile = async (): Promise<void> => {
|
|||||||
} as UserProfile
|
} as UserProfile
|
||||||
p.service_address = normalizeServiceAddress(p.service_address)
|
p.service_address = normalizeServiceAddress(p.service_address)
|
||||||
profile.value = p
|
profile.value = p
|
||||||
|
originalProfile.value = deepCloneProfile(p)
|
||||||
const createdAt = prodata.getString('created_at')
|
const createdAt = prodata.getString('created_at')
|
||||||
if (createdAt != null && createdAt != '') {
|
if (createdAt != null && createdAt != '') {
|
||||||
registeredDate.value = formatDate(createdAt)
|
registeredDate.value = formatDate(createdAt)
|
||||||
@@ -1005,54 +1202,88 @@ const loadProfile = async (): Promise<void> => {
|
|||||||
} as UserProfile
|
} as UserProfile
|
||||||
setUserProfile(newProfileData)
|
setUserProfile(newProfileData)
|
||||||
}
|
}
|
||||||
|
originalProfile.value = deepCloneProfile(profile.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildProfileUpdatePayload = (): UTSJSONObject => {
|
||||||
|
const payload = new UTSJSONObject()
|
||||||
|
const oldProfile = originalProfile.value
|
||||||
|
const newProfile = profile.value
|
||||||
|
|
||||||
|
if (oldProfile == null) {
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizeString(oldProfile.username) != normalizeString(newProfile.username)) {
|
||||||
|
payload.set('username', normalizeString(newProfile.username))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.avatar_url) != normalizeString(newProfile.avatar_url)) {
|
||||||
|
payload.set('avatar_url', normalizeString(newProfile.avatar_url))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.gender) != normalizeString(newProfile.gender)) {
|
||||||
|
payload.set('gender', normalizeString(newProfile.gender))
|
||||||
|
}
|
||||||
|
if (normalizeDateString(oldProfile.birthday) != normalizeDateString(newProfile.birthday)) {
|
||||||
|
payload.set('birthday', normalizeDateString(newProfile.birthday))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.service_address) != normalizeString(newProfile.service_address)) {
|
||||||
|
payload.set('service_address', normalizeString(newProfile.service_address))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.bio) != normalizeString(newProfile.bio)) {
|
||||||
|
payload.set('bio', normalizeString(newProfile.bio))
|
||||||
|
}
|
||||||
|
if (normalizeNumber(oldProfile.height_cm) != normalizeNumber(newProfile.height_cm)) {
|
||||||
|
payload.set('height_cm', normalizeNumber(newProfile.height_cm))
|
||||||
|
}
|
||||||
|
if (normalizeNumber(oldProfile.weight_kg) != normalizeNumber(newProfile.weight_kg)) {
|
||||||
|
payload.set('weight_kg', normalizeNumber(newProfile.weight_kg))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.preferred_language) != normalizeString(newProfile.preferred_language)) {
|
||||||
|
payload.set('preferred_language', normalizeString(newProfile.preferred_language))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.health_goal) != normalizeString(newProfile.health_goal)) {
|
||||||
|
payload.set('health_goal', normalizeString(newProfile.health_goal))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.emergency_contact) != normalizeString(newProfile.emergency_contact)) {
|
||||||
|
payload.set('emergency_contact', normalizeString(newProfile.emergency_contact))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.chronic_notes) != normalizeString(newProfile.chronic_notes)) {
|
||||||
|
payload.set('chronic_notes', normalizeString(newProfile.chronic_notes))
|
||||||
|
}
|
||||||
|
if (normalizeString(oldProfile.care_preference) != normalizeString(newProfile.care_preference)) {
|
||||||
|
payload.set('care_preference', normalizeString(newProfile.care_preference))
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
const saveProfile = async (): Promise<void> => {
|
const saveProfile = async (): Promise<void> => {
|
||||||
|
const userid: string = profileRowId.value != '' ? profileRowId.value : (profile.value.id ?? '')
|
||||||
|
const updatePayload = buildProfileUpdatePayload()
|
||||||
|
if (UTSJSONObject.keys(updatePayload).length == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '没有修改内容',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
isSaving.value = true
|
isSaving.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userid: string = profileRowId.value != '' ? profileRowId.value : (profile.value.id ?? '')
|
updatePayload.set('updated_at', new Date().toISOString())
|
||||||
const birthdayValue = profile.value.birthday != null ? profile.value.birthday.trim() : ''
|
|
||||||
console.log('saveProfile context:', JSON.stringify({
|
|
||||||
profileRowId: profileRowId.value,
|
|
||||||
profileId: profile.value.id,
|
|
||||||
birthday: birthdayValue != '' ? birthdayValue : null,
|
|
||||||
hasHealthGoal: (profile.value.health_goal ?? '') != '',
|
|
||||||
hasServiceAddress: (profile.value.service_address ?? '') != ''
|
|
||||||
}))
|
|
||||||
const baseUpdateData = new UTSJSONObject()
|
|
||||||
baseUpdateData.set('username', profile.value.username)
|
|
||||||
baseUpdateData.set('gender', profile.value.gender)
|
|
||||||
baseUpdateData.set('birthday', birthdayValue != '' ? birthdayValue : null)
|
|
||||||
baseUpdateData.set('height_cm', profile.value.height_cm)
|
|
||||||
baseUpdateData.set('weight_kg', profile.value.weight_kg)
|
|
||||||
baseUpdateData.set('bio', profile.value.bio)
|
|
||||||
baseUpdateData.set('avatar_url', profile.value.avatar_url)
|
|
||||||
|
|
||||||
const updateData = new UTSJSONObject()
|
const result = await supa
|
||||||
updateData.set('username', profile.value.username)
|
|
||||||
updateData.set('gender', profile.value.gender)
|
|
||||||
updateData.set('birthday', birthdayValue != '' ? birthdayValue : null)
|
|
||||||
updateData.set('height_cm', profile.value.height_cm)
|
|
||||||
updateData.set('weight_kg', profile.value.weight_kg)
|
|
||||||
updateData.set('bio', profile.value.bio)
|
|
||||||
updateData.set('avatar_url', profile.value.avatar_url)
|
|
||||||
updateData.set('health_goal', profile.value.health_goal)
|
|
||||||
updateData.set('service_address', profile.value.service_address)
|
|
||||||
updateData.set('emergency_contact', profile.value.emergency_contact)
|
|
||||||
updateData.set('chronic_notes', profile.value.chronic_notes)
|
|
||||||
updateData.set('care_preference', profile.value.care_preference)
|
|
||||||
|
|
||||||
let result = await supa
|
|
||||||
.from('ak_users')
|
.from('ak_users')
|
||||||
.update(updateData)
|
.update(updatePayload)
|
||||||
.eq('id', userid)
|
.eq('id', userid)
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
if (result.error == null) {
|
if (result.error == null) {
|
||||||
|
originalProfile.value = deepCloneProfile(profile.value)
|
||||||
setUserProfile(profile.value)
|
setUserProfile(profile.value)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '保存成功',
|
title: '保存成功',
|
||||||
@@ -1060,25 +1291,10 @@ const saveProfile = async (): Promise<void> => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log('saveProfile update ak_users error:', JSON.stringify(result.error))
|
console.log('saveProfile update ak_users error:', JSON.stringify(result.error))
|
||||||
result = await supa
|
|
||||||
.from('ak_users')
|
|
||||||
.update(baseUpdateData)
|
|
||||||
.eq('id', userid)
|
|
||||||
.execute()
|
|
||||||
|
|
||||||
if (result.error == null) {
|
|
||||||
setUserProfile(profile.value)
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '基础资料已保存,康养字段待后端升级',
|
title: '保存失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
console.log('saveProfile fallback update ak_users error:', JSON.stringify(result.error))
|
|
||||||
uni.showToast({
|
|
||||||
title: '保存失败,请稍后重试',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('saveProfile exception:', e)
|
console.log('saveProfile exception:', e)
|
||||||
@@ -1221,6 +1437,7 @@ const confirmBirthdayPicker = (): void => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadRecentAddressSuggestions()
|
loadRecentAddressSuggestions()
|
||||||
|
loadLanguageOptions()
|
||||||
loadProfile()
|
loadProfile()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getCurrentUserId } from '@/utils/store.uts'
|
|||||||
import { getCurrentUser } from '@/utils/store.uts'
|
import { getCurrentUser } from '@/utils/store.uts'
|
||||||
import type { UserAddress } from '@/utils/supabaseService.uts'
|
import type { UserAddress } from '@/utils/supabaseService.uts'
|
||||||
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||||
|
import type { DeliveryServiceRecordType } from '@/types/delivery.uts'
|
||||||
import {
|
import {
|
||||||
getServiceOrderStatusText,
|
getServiceOrderStatusText,
|
||||||
normalizeServiceOrderStatus,
|
normalizeServiceOrderStatus,
|
||||||
@@ -38,6 +39,25 @@ 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildHex(length: number): string {
|
||||||
|
const chars = '0123456789abcdef'
|
||||||
|
let result = ''
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUuidLike(): string {
|
||||||
|
const segment1 = buildHex(8)
|
||||||
|
const segment2 = buildHex(4)
|
||||||
|
const segment3 = '4' + buildHex(3)
|
||||||
|
const variants = '89ab'
|
||||||
|
const segment4 = variants.charAt(Math.floor(Math.random() * variants.length)) + buildHex(3)
|
||||||
|
const segment5 = buildHex(12)
|
||||||
|
return segment1 + '-' + segment2 + '-' + segment3 + '-' + segment4 + '-' + segment5
|
||||||
|
}
|
||||||
|
|
||||||
function buildOrderNo(): string {
|
function buildOrderNo(): string {
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const y = String(date.getFullYear())
|
const y = String(date.getFullYear())
|
||||||
@@ -144,6 +164,17 @@ function safeJsonField(source: any, key: string): string {
|
|||||||
return JSON.stringify(value)
|
return JSON.stringify(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function safeFirstJsonField(source: any, keys: Array<string>): string {
|
||||||
|
const plain = JSON.parse(JSON.stringify(source)) as any
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
const value = plain[keys[i]]
|
||||||
|
if (value != null) {
|
||||||
|
return JSON.stringify(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
function plainObject(source: any): any {
|
function plainObject(source: any): any {
|
||||||
return JSON.parse(JSON.stringify(source)) as any
|
return JSON.parse(JSON.stringify(source)) as any
|
||||||
}
|
}
|
||||||
@@ -168,6 +199,171 @@ function readNumber(source: any, key: string): number {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasMissingColumnError(error: any, columnName: string): boolean {
|
||||||
|
if (error == null || columnName == '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const errorText = JSON.stringify(error).toLowerCase()
|
||||||
|
return errorText.indexOf('could not find the') >= 0 && errorText.indexOf(columnName.toLowerCase()) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldUseCareTaskPath(orderId: string): boolean {
|
||||||
|
return isUuidLike(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStaffActive(staff: any): boolean {
|
||||||
|
const plain = plainObject(staff)
|
||||||
|
if (plain['deleted_at'] != null && String(plain['deleted_at']) != '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (plain['is_active'] === false) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return readNumber(staff, 'status') == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStaffPriority(staff: any): number {
|
||||||
|
let score = 0
|
||||||
|
const onlineStatus = readString(staff, 'online_status')
|
||||||
|
if (onlineStatus == 'online') {
|
||||||
|
score += 30
|
||||||
|
} else if (onlineStatus == 'resting' || onlineStatus == '') {
|
||||||
|
score += 20
|
||||||
|
} else if (onlineStatus == 'busy') {
|
||||||
|
score += 10
|
||||||
|
}
|
||||||
|
if (readString(staff, 'uid') != '') {
|
||||||
|
score += 5
|
||||||
|
}
|
||||||
|
if (readString(staff, 'station_id') != '') {
|
||||||
|
score += 1
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAutoAssignableStaff(): Promise<any | null> {
|
||||||
|
const staffResponse = await supa
|
||||||
|
.from('ml_delivery_staff')
|
||||||
|
.select('id, uid, station_id, nickname, phone, status, deleted_at, is_active, online_status, updated_at, created_at')
|
||||||
|
.eq('status', 1)
|
||||||
|
.execute()
|
||||||
|
if (staffResponse.error != null || staffResponse.data == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const rawStaffList = staffResponse.data as Array<any>
|
||||||
|
let selected: any = null
|
||||||
|
let bestScore = -1
|
||||||
|
let bestTime = ''
|
||||||
|
for (let i = 0; i < rawStaffList.length; i++) {
|
||||||
|
const staff = rawStaffList[i]
|
||||||
|
if (!isStaffActive(staff)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const score = getStaffPriority(staff)
|
||||||
|
const timeMark = readFirstString(staff, ['updated_at', 'created_at'])
|
||||||
|
if (selected == null || score > bestScore || (score == bestScore && timeMark > bestTime)) {
|
||||||
|
selected = staff
|
||||||
|
bestScore = score
|
||||||
|
bestTime = timeMark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEcServiceRequestPayload(params: CreateServiceOrderParams, userId: string, requestId: string, createdAt: string, appointmentTime: string | null, useAddressSnapshot: boolean): any {
|
||||||
|
const payload = {
|
||||||
|
id: requestId,
|
||||||
|
user_id: userId,
|
||||||
|
service_catalog_id: params.service.id,
|
||||||
|
service_name: params.service.name,
|
||||||
|
service_category: params.service.category,
|
||||||
|
elder_name: params.recipientName,
|
||||||
|
elder_phone: params.recipientPhone,
|
||||||
|
contact_name: params.contactName,
|
||||||
|
contact_phone: params.contactPhone,
|
||||||
|
scheduled_at: appointmentTime,
|
||||||
|
remark: params.remark,
|
||||||
|
status: 'ORDER_CREATED',
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: createdAt
|
||||||
|
} as any
|
||||||
|
if (useAddressSnapshot) {
|
||||||
|
payload.address_snapshot = params.address as any
|
||||||
|
} else {
|
||||||
|
payload.address_snapshot_json = params.address as any
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEcServiceRequestPayloadWithoutAddress(params: CreateServiceOrderParams, userId: string, requestId: string, createdAt: string, appointmentTime: string | null): any {
|
||||||
|
return {
|
||||||
|
id: requestId,
|
||||||
|
user_id: userId,
|
||||||
|
service_catalog_id: params.service.id,
|
||||||
|
service_name: params.service.name,
|
||||||
|
service_category: params.service.category,
|
||||||
|
elder_name: params.recipientName,
|
||||||
|
elder_phone: params.recipientPhone,
|
||||||
|
contact_name: params.contactName,
|
||||||
|
contact_phone: params.contactPhone,
|
||||||
|
scheduled_at: appointmentTime,
|
||||||
|
remark: params.remark,
|
||||||
|
status: 'ORDER_CREATED',
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: createdAt
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEcCareTaskPayload(params: CreateServiceOrderParams, userId: string, requestId: string, taskId: string, taskNo: string, createdAt: string, appointmentTime: string | null, useAddressSnapshot: boolean): any {
|
||||||
|
const payload = {
|
||||||
|
id: taskId,
|
||||||
|
task_no: taskNo,
|
||||||
|
request_id: requestId,
|
||||||
|
user_id: userId,
|
||||||
|
service_catalog_id: params.service.id,
|
||||||
|
service_name: params.service.name,
|
||||||
|
service_category: params.service.category,
|
||||||
|
service_snapshot_json: params.service as any,
|
||||||
|
elder_name: params.recipientName,
|
||||||
|
elder_phone: params.recipientPhone,
|
||||||
|
contact_name: params.contactName,
|
||||||
|
contact_phone: params.contactPhone,
|
||||||
|
scheduled_at: appointmentTime,
|
||||||
|
remark: params.remark,
|
||||||
|
status: 'ORDER_CREATED',
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: createdAt
|
||||||
|
} as any
|
||||||
|
if (useAddressSnapshot) {
|
||||||
|
payload.address_snapshot = params.address as any
|
||||||
|
} else {
|
||||||
|
payload.address_snapshot_json = params.address as any
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEcCareTaskPayloadWithoutAddress(params: CreateServiceOrderParams, userId: string, requestId: string, taskId: string, taskNo: string, createdAt: string, appointmentTime: string | null): any {
|
||||||
|
return {
|
||||||
|
id: taskId,
|
||||||
|
task_no: taskNo,
|
||||||
|
request_id: requestId,
|
||||||
|
user_id: userId,
|
||||||
|
service_catalog_id: params.service.id,
|
||||||
|
service_name: params.service.name,
|
||||||
|
service_category: params.service.category,
|
||||||
|
service_snapshot_json: params.service as any,
|
||||||
|
elder_name: params.recipientName,
|
||||||
|
elder_phone: params.recipientPhone,
|
||||||
|
contact_name: params.contactName,
|
||||||
|
contact_phone: params.contactPhone,
|
||||||
|
scheduled_at: appointmentTime,
|
||||||
|
remark: params.remark,
|
||||||
|
status: 'ORDER_CREATED',
|
||||||
|
created_at: createdAt,
|
||||||
|
updated_at: createdAt
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
function parseTimeline(item: any): ServiceOrderTimelineItemType {
|
function parseTimeline(item: any): ServiceOrderTimelineItemType {
|
||||||
return {
|
return {
|
||||||
id: readString(item, 'id'),
|
id: readString(item, 'id'),
|
||||||
@@ -231,7 +427,7 @@ function parseEvidenceFile(item: any): ServiceEvidenceFileType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>, review: ServiceReviewType | null): ServiceOrderType {
|
function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>, review: ServiceReviewType | null): ServiceOrderType {
|
||||||
const addressSnapshot = safeJsonField(item, 'address_snapshot_json')
|
const addressSnapshot = safeFirstJsonField(item, ['address_snapshot_json', 'address_snapshot'])
|
||||||
const serviceSnapshot = safeJsonField(item, 'service_snapshot_json')
|
const serviceSnapshot = safeJsonField(item, 'service_snapshot_json')
|
||||||
const addressObj = plainObject(safeParseObject(addressSnapshot))
|
const addressObj = plainObject(safeParseObject(addressSnapshot))
|
||||||
const serviceObj = plainObject(safeParseObject(serviceSnapshot))
|
const serviceObj = plainObject(safeParseObject(serviceSnapshot))
|
||||||
@@ -354,7 +550,7 @@ function readJsonObjectField(source: any, keys: Array<string>): any {
|
|||||||
|
|
||||||
function mapCareTaskRowToLegacyOrderRow(item: any): any {
|
function mapCareTaskRowToLegacyOrderRow(item: any): any {
|
||||||
const serviceSnapshotValue = readJsonObjectField(item, ['service_snapshot_json'])
|
const serviceSnapshotValue = readJsonObjectField(item, ['service_snapshot_json'])
|
||||||
const addressSnapshotValue = readJsonObjectField(item, ['address_snapshot_json'])
|
const addressSnapshotValue = readJsonObjectField(item, ['address_snapshot', 'address_snapshot_json'])
|
||||||
let derivedStatus = readString(item, 'status')
|
let derivedStatus = readString(item, 'status')
|
||||||
if (readFirstString(item, ['accepted_by_family_at']) != '') {
|
if (readFirstString(item, ['accepted_by_family_at']) != '') {
|
||||||
derivedStatus = 'ACCEPTED'
|
derivedStatus = 'ACCEPTED'
|
||||||
@@ -478,6 +674,9 @@ function buildLegacyExecutionRecord(taskId: string, records: Array<any>): Servic
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getCareTaskDetail(taskId: string): Promise<ServiceOrderType | null> {
|
async function getCareTaskDetail(taskId: string): Promise<ServiceOrderType | null> {
|
||||||
|
if (!isUuidLike(taskId)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
const taskResponse = await supa.from('ec_care_tasks').select('*').eq('id', taskId).limit(1).execute()
|
const taskResponse = await supa.from('ec_care_tasks').select('*').eq('id', taskId).limit(1).execute()
|
||||||
if (taskResponse.error != null || taskResponse.data == null) {
|
if (taskResponse.error != null || taskResponse.data == null) {
|
||||||
return null
|
return null
|
||||||
@@ -545,66 +744,47 @@ async function tryCreateCareTask(params: CreateServiceOrderParams): Promise<Serv
|
|||||||
if (userId == '') {
|
if (userId == '') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const requestId = buildId('sr')
|
const requestId = buildUuidLike()
|
||||||
const taskId = buildId('ct')
|
const taskId = buildUuidLike()
|
||||||
const taskNo = buildOrderNo()
|
const taskNo = buildOrderNo()
|
||||||
const createdAt = nowIso()
|
const createdAt = nowIso()
|
||||||
const appointmentTime = normalizeAppointmentTime(params.appointmentTime)
|
const appointmentTime = normalizeAppointmentTime(params.appointmentTime)
|
||||||
const requestResponse = await supa.from('ec_service_requests').insert({
|
let requestResponse = await supa.from('ec_service_requests').insert(
|
||||||
id: requestId,
|
buildEcServiceRequestPayload(params, userId, requestId, createdAt, appointmentTime, true)
|
||||||
user_id: userId,
|
).execute()
|
||||||
service_catalog_id: params.service.id,
|
if (requestResponse.error != null) {
|
||||||
service_name: params.service.name,
|
requestResponse = await supa.from('ec_service_requests').insert(
|
||||||
service_category: params.service.category,
|
buildEcServiceRequestPayload(params, userId, requestId, createdAt, appointmentTime, false)
|
||||||
elder_name: params.recipientName,
|
).execute()
|
||||||
elder_phone: params.recipientPhone,
|
}
|
||||||
contact_name: params.contactName,
|
if (requestResponse.error != null) {
|
||||||
contact_phone: params.contactPhone,
|
requestResponse = await supa.from('ec_service_requests').insert(
|
||||||
address_snapshot_json: params.address as any,
|
buildEcServiceRequestPayloadWithoutAddress(params, userId, requestId, createdAt, appointmentTime)
|
||||||
scheduled_at: appointmentTime,
|
).execute()
|
||||||
remark: params.remark,
|
}
|
||||||
status: 'ORDER_CREATED',
|
|
||||||
created_at: createdAt,
|
|
||||||
updated_at: createdAt
|
|
||||||
}).execute()
|
|
||||||
if (requestResponse.error != null) {
|
if (requestResponse.error != null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const taskResponse = await supa.from('ec_care_tasks').insert({
|
let taskResponse = await supa.from('ec_care_tasks').insert(
|
||||||
id: taskId,
|
buildEcCareTaskPayload(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime, true)
|
||||||
task_no: taskNo,
|
).execute()
|
||||||
request_id: requestId,
|
if (taskResponse.error != null) {
|
||||||
user_id: userId,
|
taskResponse = await supa.from('ec_care_tasks').insert(
|
||||||
service_catalog_id: params.service.id,
|
buildEcCareTaskPayload(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime, false)
|
||||||
service_name: params.service.name,
|
).execute()
|
||||||
service_category: params.service.category,
|
}
|
||||||
service_snapshot_json: params.service as any,
|
if (taskResponse.error != null) {
|
||||||
address_snapshot_json: params.address as any,
|
taskResponse = await supa.from('ec_care_tasks').insert(
|
||||||
elder_name: params.recipientName,
|
buildEcCareTaskPayloadWithoutAddress(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime)
|
||||||
elder_phone: params.recipientPhone,
|
).execute()
|
||||||
contact_name: params.contactName,
|
}
|
||||||
contact_phone: params.contactPhone,
|
|
||||||
scheduled_at: appointmentTime,
|
|
||||||
remark: params.remark,
|
|
||||||
status: 'ORDER_CREATED',
|
|
||||||
created_at: createdAt,
|
|
||||||
updated_at: createdAt
|
|
||||||
}).execute()
|
|
||||||
if (taskResponse.error != null) {
|
if (taskResponse.error != null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
await insertWorkOrderEvent(taskId, '', 'ORDER_CREATED', userId, 'consumer', 'create_task', '创建服务申请')
|
await insertWorkOrderEvent(taskId, '', 'ORDER_CREATED', userId, 'consumer', 'create_task', '创建服务申请')
|
||||||
const staffResponse = await supa
|
const assignedStaff = await getAutoAssignableStaff()
|
||||||
.from('ml_delivery_staff')
|
if (assignedStaff != null) {
|
||||||
.select('id, uid, station_id, nickname, phone, status, deleted_at')
|
const assignedUserId = readString(assignedStaff, 'uid')
|
||||||
.eq('status', 1)
|
|
||||||
.order('created_at', { ascending: true })
|
|
||||||
.execute()
|
|
||||||
if (staffResponse.data != null) {
|
|
||||||
const rawStaffList = staffResponse.data as Array<any>
|
|
||||||
if (rawStaffList.length > 0) {
|
|
||||||
const staffObj = rawStaffList[0]
|
|
||||||
const assignedUserId = readFirstString(staffObj, ['uid', 'id'])
|
|
||||||
if (assignedUserId != '') {
|
if (assignedUserId != '') {
|
||||||
await supa.from('ec_care_tasks').update({
|
await supa.from('ec_care_tasks').update({
|
||||||
status: 'ORDER_ASSIGNED',
|
status: 'ORDER_ASSIGNED',
|
||||||
@@ -614,7 +794,6 @@ async function tryCreateCareTask(params: CreateServiceOrderParams): Promise<Serv
|
|||||||
await insertWorkOrderEvent(taskId, 'ORDER_CREATED', 'ORDER_ASSIGNED', userId, 'system', 'assign_task', '系统已自动派单')
|
await insertWorkOrderEvent(taskId, 'ORDER_CREATED', 'ORDER_ASSIGNED', userId, 'system', 'assign_task', '系统已自动派单')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return await getCareTaskDetail(taskId)
|
return await getCareTaskDetail(taskId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,22 +907,15 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
await insertLegacyStatusLog(orderId, '', 'created', userId, 'consumer', '创建服务订单')
|
await insertLegacyStatusLog(orderId, '', 'created', userId, 'consumer', '创建服务订单')
|
||||||
const staffResponse = await supa
|
const staffObj = await getAutoAssignableStaff()
|
||||||
.from('ml_delivery_staff')
|
if (staffObj != null) {
|
||||||
.select('id, station_id, nickname, phone, status, deleted_at')
|
const plainStaff = plainObject(staffObj)
|
||||||
.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')
|
const assignmentId = buildId('sa')
|
||||||
await supa.from('hss_service_assignments').insert({
|
await supa.from('hss_service_assignments').insert({
|
||||||
id: assignmentId,
|
id: assignmentId,
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
staff_id: readString(staffObj, 'id'),
|
staff_id: readString(plainStaff, 'id'),
|
||||||
station_id: readString(staffObj, 'station_id') == '' ? null : readString(staffObj, 'station_id'),
|
station_id: readString(plainStaff, 'station_id') == '' ? null : readString(plainStaff, 'station_id'),
|
||||||
status: 'assigned',
|
status: 'assigned',
|
||||||
assigned_at: now,
|
assigned_at: now,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
@@ -752,12 +924,11 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
|
|||||||
await supa.from('hss_service_orders').update({
|
await supa.from('hss_service_orders').update({
|
||||||
status: 'assigned',
|
status: 'assigned',
|
||||||
current_assignment_id: assignmentId,
|
current_assignment_id: assignmentId,
|
||||||
current_staff_id: readString(staffObj, 'id'),
|
current_staff_id: readString(plainStaff, 'id'),
|
||||||
updated_at: now
|
updated_at: now
|
||||||
}).eq('id', orderId).execute()
|
}).eq('id', orderId).execute()
|
||||||
await insertLegacyStatusLog(orderId, 'created', 'assigned', userId, 'system', '系统已自动派单')
|
await insertLegacyStatusLog(orderId, 'created', 'assigned', userId, 'system', '系统已自动派单')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return await getLegacyServiceOrderDetail(orderId)
|
return await getLegacyServiceOrderDetail(orderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,19 +965,94 @@ export async function listConsumerServiceOrders(): Promise<Array<ServiceOrderTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getServiceOrderDetail(orderId: string): Promise<ServiceOrderType | null> {
|
export async function getServiceOrderDetail(orderId: string): Promise<ServiceOrderType | null> {
|
||||||
|
if (shouldUseCareTaskPath(orderId)) {
|
||||||
const careTask = await getCareTaskDetail(orderId)
|
const careTask = await getCareTaskDetail(orderId)
|
||||||
if (careTask != null) {
|
if (careTask != null) {
|
||||||
return careTask
|
return careTask
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return await getLegacyServiceOrderDetail(orderId)
|
return await getLegacyServiceOrderDetail(orderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveServiceRecord(orderId: string, record: DeliveryServiceRecordType): Promise<ServiceOrderType | null> {
|
||||||
|
const userId = getCurrentUserId()
|
||||||
|
if (userId == '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||||
|
if (careTask != null) {
|
||||||
|
const savedAt = nowIso()
|
||||||
|
const serviceContent = record.serviceContent.length > 0 ? record.serviceContent : record.serviceItems.map(item => item.name)
|
||||||
|
const serviceSummary = record.serviceSummary != '' ? record.serviceSummary : record.processNote
|
||||||
|
const healthMetrics = record.healthMetrics as any
|
||||||
|
const familyConfirmation = record.familyConfirmation as any
|
||||||
|
const insertResponse = await supa.from('ec_care_records').insert({
|
||||||
|
id: buildId('care-record'),
|
||||||
|
task_id: orderId,
|
||||||
|
record_type: 'service_record',
|
||||||
|
created_by: userId,
|
||||||
|
service_items_json: record.serviceItems as any,
|
||||||
|
service_content_json: serviceContent as any,
|
||||||
|
service_summary: serviceSummary,
|
||||||
|
process_note: record.processNote,
|
||||||
|
elder_status: record.elderStatus,
|
||||||
|
health_metrics_json: healthMetrics,
|
||||||
|
materials_used: record.materialsUsed,
|
||||||
|
abnormal_note: record.abnormalNote,
|
||||||
|
photos_json: record.photos as any,
|
||||||
|
staff_remark: record.staffRemark,
|
||||||
|
family_confirmation_json: familyConfirmation,
|
||||||
|
created_at: savedAt,
|
||||||
|
updated_at: savedAt
|
||||||
|
}).execute()
|
||||||
|
if (insertResponse.error != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||||
|
updated_at: savedAt
|
||||||
|
}).eq('id', orderId).execute()
|
||||||
|
if (updateResponse.error != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
await insertWorkOrderEvent(orderId, careTask.status, careTask.status, userId, 'staff', 'save_service_record', serviceSummary == '' ? '服务记录已保存' : serviceSummary)
|
||||||
|
return await getCareTaskDetail(orderId)
|
||||||
|
}
|
||||||
|
const current = await getLegacyServiceOrderDetail(orderId)
|
||||||
|
if (current == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const savedAt = nowIso()
|
||||||
|
const serviceSummary = record.serviceSummary != '' ? record.serviceSummary : record.processNote
|
||||||
|
const saveResponse = await supa.from('hss_service_execution_records').insert({
|
||||||
|
id: buildId('ser'),
|
||||||
|
order_id: orderId,
|
||||||
|
checkin_time: current.executionRecord != null ? current.executionRecord.checkin_time : savedAt,
|
||||||
|
checkin_latitude: current.executionRecord != null ? current.executionRecord.checkin_latitude : 0,
|
||||||
|
checkin_longitude: current.executionRecord != null ? current.executionRecord.checkin_longitude : 0,
|
||||||
|
checkin_address: current.executionRecord != null ? current.executionRecord.checkin_address : '',
|
||||||
|
service_content_json: record.serviceContent.length > 0 ? record.serviceContent as any : record.serviceItems.map(item => item.name) as any,
|
||||||
|
service_summary: serviceSummary,
|
||||||
|
completion_images_json: record.photos as any,
|
||||||
|
signature_image: '',
|
||||||
|
signature_name: record.familyConfirmation.familyMember,
|
||||||
|
created_at: savedAt,
|
||||||
|
updated_at: savedAt
|
||||||
|
}).execute()
|
||||||
|
if (saveResponse.error != null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
await supa.from('hss_service_orders').update({
|
||||||
|
updated_at: savedAt
|
||||||
|
}).eq('id', orderId).execute()
|
||||||
|
return await getLegacyServiceOrderDetail(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
export async function confirmServiceOrder(orderId: string, rating: number, content: string, tags: Array<string>): Promise<ServiceOrderType | null> {
|
export async function confirmServiceOrder(orderId: string, rating: number, content: string, tags: Array<string>): Promise<ServiceOrderType | null> {
|
||||||
const userId = getCurrentUserId()
|
const userId = getCurrentUserId()
|
||||||
if (userId == '') {
|
if (userId == '') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const careTask = await getCareTaskDetail(orderId)
|
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||||
if (careTask != null) {
|
if (careTask != null) {
|
||||||
const acceptedAt = nowIso()
|
const acceptedAt = nowIso()
|
||||||
const updateResponse = await supa.from('ec_care_tasks').update({
|
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||||
@@ -870,7 +1116,7 @@ export async function rejectServiceOrderAcceptance(orderId: string, content: str
|
|||||||
if (userId == '') {
|
if (userId == '') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const careTask = await getCareTaskDetail(orderId)
|
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||||
if (careTask != null) {
|
if (careTask != null) {
|
||||||
const rejectedAt = nowIso()
|
const rejectedAt = nowIso()
|
||||||
const updateResponse = await supa.from('ec_care_tasks').update({
|
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||||
@@ -915,74 +1161,3 @@ export async function rejectServiceOrderAcceptance(orderId: string, content: str
|
|||||||
export async function getCurrentConsumerUser() {
|
export async function getCurrentConsumerUser() {
|
||||||
return await getCurrentUser()
|
return await getCurrentUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrdersByTab(tab: string): Promise<Array<ServiceOrderType>> {
|
|
||||||
const orders = await listConsumerServiceOrders()
|
|
||||||
if (tab == 'pending') {
|
|
||||||
return orders.filter((order) => {
|
|
||||||
return order.status != 'ACCEPTED' && order.status != 'COMPLETED' && order.status != 'CANCELLED'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (tab == 'history') {
|
|
||||||
return orders.filter((order) => {
|
|
||||||
return order.status == 'ACCEPTED' || order.status == 'COMPLETED' || order.status == 'CANCELLED'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return orders.filter((order) => {
|
|
||||||
return order.status != 'ACCEPTED' && order.status != 'COMPLETED' && order.status != 'CANCELLED'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrderDetail(orderId: string): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDashboard(): Promise<UTSJSONObject> {
|
|
||||||
const orders = await listConsumerServiceOrders()
|
|
||||||
let pendingCount = 0
|
|
||||||
let todayCount = 0
|
|
||||||
let completedCount = 0
|
|
||||||
for (let i = 0; i < orders.length; i++) {
|
|
||||||
const order = orders[i]
|
|
||||||
if (order.status == 'ACCEPTED' || order.status == 'COMPLETED' || order.status == 'CANCELLED') {
|
|
||||||
completedCount += 1
|
|
||||||
} else {
|
|
||||||
pendingCount += 1
|
|
||||||
todayCount += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
pendingCount,
|
|
||||||
todayCount,
|
|
||||||
completedCount,
|
|
||||||
totalCount: orders.length
|
|
||||||
} as UTSJSONObject
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function acceptOrder(orderId: string): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function departOrder(orderId: string, _location: any): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function arriveOrder(orderId: string, _location: any): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkinOrder(orderId: string, _payload: any): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function startService(orderId: string): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function finishOrder(orderId: string): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function saveServiceRecord(orderId: string, _payload: any): Promise<ServiceOrderType | null> {
|
|
||||||
return await getServiceOrderDetail(orderId)
|
|
||||||
}
|
|
||||||
183
报错信息.txt
183
报错信息.txt
@@ -1,159 +1,24 @@
|
|||||||
mp.esm.js:529 saveProfile context: {"profileRowId":"b653fded-7d5e-4950-aa0d-725595543e3c","profileId":"b653fded-7d5e-4950-aa0d-725595543e3c","birthday":null,"hasHealthGoal":false,"hasServiceAddress":true}
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: uid=eq.67251404-ad43-4118-a6d4-74ca16351427
|
||||||
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ak_users filter: id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&uid=eq.67251404-ad43-4118-a6d4-74ca16351427
|
||||||
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...Tozk | auth-mode: pre-set | prefer: return=representation
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
uni.api.esm.js:1042 PATCH http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ml_delivery_staff?id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
(anonymous) @ uni.api.esm.js:1042
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: return=representation
|
||||||
invokeApi @ uni.api.esm.js:330
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: uid=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
promiseApi @ uni.api.esm.js:889
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&uid=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
(anonymous) @ ak-req.uts:214
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
doOnce @ ak-req.uts:213
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
_loop$ @ ak-req.uts:328
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: uid=eq.67251404-ad43-4118-a6d4-74ca16351427
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&uid=eq.67251404-ad43-4118-a6d4-74ca16351427
|
||||||
s @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
_ @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ml_delivery_staff?id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: return=representation
|
||||||
fulfilled @ uni.mp.esm.js:1134
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: uid=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
Promise.then (async)
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&uid=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
step @ uni.mp.esm.js:1134
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
(anonymous) @ uni.mp.esm.js:1134
|
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
__awaiter @ uni.mp.esm.js:1134
|
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=*%2C%20station%3Aml_delivery_stations(id%2C%20name)&limit=1&id=eq.399bc088-ab36-4f68-a993-e34aec421e8c
|
||||||
request @ ak-req.uts:148
|
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...-2B0 | auth-mode: pre-set | prefer: count=exact
|
||||||
_callee19$ @ aksupa.uts:1290
|
|
||||||
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
|
|
||||||
requestWithAutoRefresh @ aksupa.uts:1289
|
|
||||||
_callee14$ @ aksupa.uts:1143
|
|
||||||
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
|
|
||||||
update @ aksupa.uts:1119
|
|
||||||
_callee$ @ aksupa.uts:477
|
|
||||||
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
|
|
||||||
execute @ aksupa.uts:369
|
|
||||||
_callee2$ @ profile.uvue:1057
|
|
||||||
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
|
|
||||||
saveProfile @ profile.uvue:1015
|
|
||||||
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] HTTP error response(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:304
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] status: 400(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:305
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] url: http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:306
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] body: {"code":"22P02","details":null,"hint":null,"message":"invalid input syntax for type uuid: \"zh-CN\""}(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:307
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 saveProfile update ak_users error: {"errSubject":"AppError","errCode":-1,"errMsg":"请求失败: 400"}
|
|
||||||
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ak_users filter: id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
|
||||||
mp.esm.js:529 [ak-req] PATCH http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
|
|
||||||
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...Tozk | auth-mode: pre-set | prefer: return=representation
|
|
||||||
uni.api.esm.js:1042 PATCH http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(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:328
|
|
||||||
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
|
|
||||||
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
|
|
||||||
_callee19$ @ aksupa.uts:1290
|
|
||||||
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
|
|
||||||
requestWithAutoRefresh @ aksupa.uts:1289
|
|
||||||
_callee14$ @ aksupa.uts:1143
|
|
||||||
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
|
|
||||||
update @ aksupa.uts:1119
|
|
||||||
_callee$ @ aksupa.uts:477
|
|
||||||
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
|
|
||||||
execute @ aksupa.uts:369
|
|
||||||
_callee2$ @ profile.uvue:1071
|
|
||||||
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
|
|
||||||
saveProfile @ profile.uvue:1015
|
|
||||||
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 [ak-req] HTTP error response(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:304
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] status: 400(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:305
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] url: http://119.146.131.237:9126/rest/v1/ak_users?id=eq.b653fded-7d5e-4950-aa0d-725595543e3c(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:306
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 [ak-req] body: {"code":"22P02","details":null,"hint":null,"message":"invalid input syntax for type uuid: \"zh-CN\""}(env: Windows,mp,1.06.2504030; lib: 3.16.0)
|
|
||||||
(anonymous) @ mp.esm.js:529
|
|
||||||
__f__ @ uni.api.esm.js:590
|
|
||||||
success @ ak-req.uts:307
|
|
||||||
(anonymous) @ uni.api.esm.js:946
|
|
||||||
mp.esm.js:529 saveProfile fallback update ak_users error: {"errSubject":"AppError","errCode":-1,"errMsg":"请求失败: 400"}
|
|
||||||
Reference in New Issue
Block a user